Skip to content

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:11434Ollama 默认监听 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:8000vLLM 默认监听 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-utilizationvLLM 占用显存的比例,新版本默认 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_urlmodel
Ollamahttp://localhost:11434/v1qwen3:0.6b
vLLMhttp://localhost:8000/v1qwen3-0.6b
SGLanghttp://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 之间的间隔
P9999% 的请求都比这个值快,衡量尾延迟

练习:把 --request-rate 依次设为 1、2、4、8、16、32,记录吞吐和 P99 TTFT,画出“请求速率—延迟”曲线。你会看到负载超过某个点后延迟急剧上升,这个拐点就是服务的容量。

Ollama 与 vLLM 怎么选 ​

OllamavLLM / 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 部署,在相同并发下对比吞吐和延迟。

作者:崔涵帅、姚智、王振胜、唐志清 · 边缘智能系统实验室