深色模式
L1 大模型部署与压测
目标
- 用 Ollama 和 vLLM 两种方式部署一个大模型服务;
- 通过 OpenAI 兼容接口调用文本模型和多模态模型;
- 测量服务的首字延迟(TTFT)、吞吐量,画出负载与延迟的关系曲线。
这是所有方向的基础实验:做研究时,你几乎每天都要部署模型、调用接口、测量性能。
环境
- Linux 服务器或 WSL2,一张 NVIDIA 显卡。8 GB 显存时,vLLM 可以跑 0.6B~1.7B 的模型(BF16 权重);Ollama 默认用 4 比特量化,可以跑到 4B
- NVIDIA 驱动 ≥ 580:当前的 vLLM 镜像基于 CUDA 13。驱动较旧,或者显卡是 V100 等老卡时,使用对应 CUDA 12 的旧版本 vLLM 镜像(以 vLLM 发布说明为准),或者先只做 Ollama 部分
- 已安装 Docker 和 NVIDIA Container Toolkit(见 Docker)
- Python 环境中安装
openai:uv pip install openai
没有 GPU 服务器怎么办
可以先只做 Ollama 部分:在自己的电脑上(CPU 或 Mac)直接安装 Ollama,完成第 2 步和第 4 步的文本对话。vLLM 部分等有了服务器账号再做,也可以用 Kaggle、AutoDL 等平台的 GPU。
第 1 步:下载模型
本实验用 Qwen3-0.6B,体积小、下载快。熟悉之后可以换成更大的模型,或者 2026 年初发布、专为端侧设计的 Qwen3.5 小模型(0.8B、2B、4B、9B)。
从 ModelScope 下载(国内推荐)
bash
uv pip install modelscope
modelscope download --model Qwen/Qwen3-0.6B --local_dir ~/models/Qwen3-0.6B从 Hugging Face 下载
bash
uv pip install -U huggingface_hub
export HF_ENDPOINT=https://hf-mirror.com # 国内可用镜像加速
hf download Qwen/Qwen3-0.6B --local-dir ~/models/Qwen3-0.6B也可以在 ModelScope 或 Hugging Face 网页上找到模型,在“模型文件”页手动下载。下载完成后目录里应该有 config.json、model.safetensors、tokenizer.json 等文件。
第 2 步:用 Ollama 部署
Ollama 安装简单、镜像小、支持 Linux/Mac/Windows,适合快速体验和端侧部署;缺点是并发性能不如专门优化的推理框架。它使用自己的模型格式(GGUF),会自动从 Ollama 模型库下载,不需要第 1 步下载的文件。
方式一:直接安装
bash
curl -fsSL https://ollama.com/install.sh | sh
ollama run qwen3:0.6b # 首次运行会自动下载模型,然后进入对话方式二:Docker 部署
bash
docker run -d --gpus=all \
-v ollama:/root/.ollama \
-p 11434:11434 \
-e OLLAMA_NUM_PARALLEL=4 \
-e OLLAMA_KEEP_ALIVE=-1 \
--name ollama \
ollama/ollama
docker exec -it ollama ollama run qwen3:0.6b| 参数 | 作用 |
|---|---|
-v ollama:/root/.ollama | 模型文件保存在数据卷里,删除容器后不用重新下载。也可以挂载宿主机上已有的模型目录 |
-p 11434:11434 | Ollama 默认监听 11434 端口;需要换宿主机端口时改前面的数字,例如 -p 12555:11434 |
OLLAMA_NUM_PARALLEL | 每个模型同时处理的最大请求数 |
OLLAMA_KEEP_ALIVE=-1 | 模型加载后一直常驻显存,避免空闲后被卸载、下次请求重新加载 |
OLLAMA_MAX_LOADED_MODELS | 同时加载的模型数量上限 |
模型标签以 Ollama 模型库为准,例如 Qwen3.5 小模型是 qwen3.5:0.8b。
第 3 步:用 vLLM 部署
vLLM 是目前最常用的高性能推理框架,核心技术是 PagedAttention 和连续批处理,适合高并发服务和科研实验。
先停掉 Ollama
vLLM 启动时会检查显卡的空闲显存,少于“总显存 × --gpu-memory-utilization(默认 0.92)”就直接报错退出,不会自动少占一点。第 2 步设置了 OLLAMA_KEEP_ALIVE=-1,模型一直占着显存,所以先停掉 Ollama:docker stop ollama(直接安装的用 ollama stop qwen3:0.6b 卸载模型)。之后需要再用 Ollama 时,docker start ollama 即可。
bash
docker run -d --gpus all --ipc=host \
-p 8000:8000 \
-v ~/models:/models \
--name vllm-qwen \
vllm/vllm-openai:latest \
--model /models/Qwen3-0.6B \
--served-model-name qwen3-0.6b \
--max-model-len 8192
docker logs -f vllm-qwen # 看到 "Application startup complete" 说明启动成功参数说明
| 参数 | 作用 |
|---|---|
--gpus all | 允许容器使用全部 GPU;只用第 0 张卡写 --gpus '"device=0"' |
--ipc=host | 共享宿主机的进程间通信资源,vLLM 多进程需要较大的共享内存 |
-p 8000:8000 | vLLM 默认监听 8000 端口 |
-v ~/models:/models | 把宿主机的模型目录挂载进容器,容器里的 /models/Qwen3-0.6B 就是宿主机的 ~/models/Qwen3-0.6B |
--model | 容器内的模型路径,也可以直接写 Hugging Face 模型名让 vLLM 自动下载 |
--served-model-name | 接口里使用的模型名,调用时 model 参数填它 |
--max-model-len | 最大上下文长度。显存不够时调小 |
--gpu-memory-utilization | vLLM 占用显存的比例,新版本默认 0.92(旧版本为 0.9)。启动时空闲显存不够这个比例会直接报错;和别人或其他服务共用显卡时调小 |
--tensor-parallel-size | 用几张卡做张量并行,跑大模型时使用 |
不用 Docker 的方式
bash
uv pip install vllm
export VLLM_USE_MODELSCOPE=True # 让 vLLM 从 ModelScope 下载模型
vllm serve Qwen/Qwen3-0.6B --served-model-name qwen3-0.6b其他推理框架:SGLang 和 vLLM 同为主流框架,前缀缓存和结构化生成做得好,启动方式类似:python -m sglang.launch_server --model-path ~/models/Qwen3-0.6B --port 30000。
第 4 步:调用接口
Ollama、vLLM、SGLang 都提供和 OpenAI 一样的接口,同一段代码改 base_url 和 model 就能切换。
| 服务 | base_url | model |
|---|---|---|
| Ollama | http://localhost:11434/v1 | qwen3:0.6b |
| vLLM | http://localhost:8000/v1 | qwen3-0.6b |
| SGLang | http://localhost:30000/v1 | 启动时 --model-path 指定的模型(或用 --served-model-name 指定的名字) |
文本对话
python
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
resp = client.chat.completions.create(
model="qwen3-0.6b",
messages=[{"role": "user", "content": "用一句话解释什么是边缘计算"}],
max_tokens=256,
# Qwen3 默认先输出思考过程;做性能测试时可以关掉(vLLM 支持此参数)
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)
print(resp.choices[0].message.content)流式输出与测量首字延迟
python
import time
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")
def measure(prompt, model="qwen3-0.6b"):
start = time.perf_counter()
first, chunks = None, 0
stream = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
max_tokens=256,
stream=True,
extra_body={"chat_template_kwargs": {"enable_thinking": False}},
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
if first is None:
first = time.perf_counter()
chunks += 1
end = time.perf_counter()
if first is None:
print("没有收到输出")
return
print(f"首字延迟 TTFT: {(first - start) * 1000:.0f} ms")
print(f"平均每个输出块耗时(近似 TPOT): {(end - first) / max(chunks - 1, 1) * 1000:.1f} ms,共 {chunks} 块")
measure("介绍一下 Transformer 的注意力机制")多模态模型(图片问答)
部署一个视觉语言模型,例如 Qwen3-VL-2B-Instruct(Qwen3.5 小模型也原生支持图片输入)。先下载模型:
bash
modelscope download --model Qwen/Qwen3-VL-2B-Instruct --local_dir ~/models/Qwen3-VL-2B-Instruct上一步的 vLLM 已经占了约 92% 的显存,在同一张卡上再启动一个 vLLM 一定会失败。三种做法任选一种:
- 先停掉文本模型(推荐):
docker stop vllm-qwen,再启动下面的容器; - 两个模型同时运行:两个容器都加上
--gpu-memory-utilization 0.45(文本模型要先删掉容器再按新参数启动); - 有多张卡:把
--gpus all改成--gpus '"device=1"',放到另一张卡上。
bash
docker run -d --gpus all --ipc=host -p 8001:8000 -v ~/models:/models --name vllm-vl \
vllm/vllm-openai:latest --model /models/Qwen3-VL-2B-Instruct --served-model-name qwen3-vl-2b用异步客户端并发提问:
python
import asyncio
import base64
from openai import AsyncOpenAI
client = AsyncOpenAI(base_url="http://localhost:8001/v1", api_key="EMPTY")
async def ask_image(image_path, question, model="qwen3-vl-2b"):
with open(image_path, "rb") as f:
b64 = base64.b64encode(f.read()).decode()
resp = await client.chat.completions.create(
model=model,
messages=[{
"role": "user",
"content": [
{"type": "text", "text": question},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}},
],
}],
)
return resp.choices[0].message.content
async def main():
questions = ["图里有什么?", "图里有几个人?", "用一句话描述这个场景"]
answers = await asyncio.gather(*(ask_image("test.jpg", q) for q in questions))
for q, a in zip(questions, answers):
print(q, "→", a)
asyncio.run(main())使用课题组的模型服务
课题组服务器上部署了更大的模型,可以向带你的博士生申请 API 地址和 Key,调用方式和上面完全一样。API Key 不要写进代码提交到 Git,用环境变量传入。
第 5 步:压测
vllm bench serve 按指定的请求速率向服务发送请求,统计延迟和吞吐。vLLM 镜像里已经带了这个工具,直接在容器里运行。如果上一步停掉了 vllm-qwen,先 docker stop vllm-vl,再 docker start vllm-qwen:
bash
docker exec -it vllm-qwen vllm bench serve \
--model /models/Qwen3-0.6B \
--served-model-name qwen3-0.6b \
--dataset-name random \
--random-input-len 512 \
--random-output-len 128 \
--num-prompts 200 \
--request-rate 8参数以 vllm bench serve --help 为准。输出中最重要的指标:
| 指标 | 含义 |
|---|---|
| Request throughput | 每秒完成的请求数 |
| Output token throughput | 每秒生成的 token 数 |
| TTFT(Time to First Token) | 从发出请求到收到第一个 token 的时间,用户感受到的“反应速度” |
| TPOT(Time per Output Token) | 平均每生成一个 token 的时间,决定“打字速度” |
| ITL(Inter-token Latency) | 相邻两个 token 之间的间隔 |
| P99 | 99% 的请求都比这个值快,衡量尾延迟 |
练习:把 --request-rate 依次设为 1、2、4、8、16、32,记录吞吐和 P99 TTFT,画出“请求速率—延迟”曲线。你会看到负载超过某个点后延迟急剧上升,这个拐点就是服务的容量。
Ollama 与 vLLM 怎么选
| Ollama | vLLM / SGLang | |
|---|---|---|
| 上手难度 | 很低 | 中等 |
| 并发性能 | 一般 | 高(连续批处理、PagedAttention、前缀缓存) |
| 硬件 | CPU、Mac、Jetson、各种 GPU | 主要是数据中心 GPU,也支持部分其他硬件 |
| 适合 | 本地体验、端侧部署、快速原型 | 高并发服务、科研实验、性能对比 |
常见问题
- 启动时报
Free memory on device ... is less than desired GPU memory utilization:显卡上的空闲显存不够--gpu-memory-utilization指定的比例。用nvidia-smi看是谁占着显存:停掉 Ollama 或另一个 vLLM 容器,或者把这个参数调小(例如两个实例各 0.45)。 - 显存不足(CUDA out of memory):调小
--max-model-len,或调小--gpu-memory-utilization;确认显卡上没有别的进程(nvidia-smi)。 CUDA driver version is insufficient:驱动版本低于镜像需要的 CUDA 版本,见上面“环境”一节。- 容器启动后很快退出:
docker logs vllm-qwen看报错,常见原因是模型路径写错(注意是容器内的路径)。 - 共享内存不足:没加
--ipc=host。 - 下载模型很慢:改用 ModelScope,或设置
HF_ENDPOINT。
延伸
- 读 nano-vllm 的源码,画出一个请求在系统里经过的模块。
- 开启和关闭前缀缓存(
--no-enable-prefix-caching),用很多请求共享同一段长系统提示词的负载做对比,看 TTFT 的变化。 - 同一个模型分别用 Ollama 和 vLLM 部署,在相同并发下对比吞吐和延迟。