AI 工具

用 vLLM 的 Docker 镜像在 GPU 服务器上部署 Qwen3-8B

用 vLLM 的 Docker 镜像在 GPU 服务器上部署 Qwen3-8B,提供带密钥的 OpenAI 兼容接口,开启思考内容解析和工具调用解析。再写一个压测脚本,测首字延迟(TTFT)和吞吐,帮你决定并发数和机器配置。

BINBHO · 图文教程

用 vLLM 的 Docker 镜像在 GPU 服务器上部署 Qwen3-8B,提供带密钥的 OpenAI 兼容接口,开启思考内容解析和工具调用解析。再写一个压测脚本,测首字延迟(TTFT)和吞吐,帮你决定并发数和机器配置。

先看清成品

  • vLLM 部分没有在 box 上运行(box 没有 GPU):docker-compose.vllm.yml 只做了 docker compose config 校验。参数来自 Qwen 官方的 vLLM 部署文档和 vLLM 的 Docker 文档。

  • 压测脚本在 box 上实测过,目标是 Ollama 加 Qwen3-0.6B(CPU):

    并发 吞吐 TTFT 中位数
    1 102.4 tok/s 0.04 s
    2 139.0 tok/s 0.06 s
    4 232.9 tok/s 0.11 s

    测试时设置了 OLLAMA_NUM_PARALLEL=4 并关闭思考。之前另一次运行的数字是 135.6 / 118.5 / 239.3,那次并发 2 比并发 1 还低,说明单次结果波动不小,要多跑几次。

合规提醒:Qwen3 系列开源模型是 Apache-2.0 许可(以 Hugging Face 模型页为准),商用前请自己再确认一遍。本地部署时资料不出本机,但 Ollama 的 11434 和 vLLM 的 8000 端口不要暴露到公网;vLLM 默认不鉴权,本文强制设置 --api-key。用百炼(DashScope)云端 API 时,数据会发到阿里云,敏感数据先脱敏。价格以阿里云百炼的计费页为准,本文不列价格。

做完之后你会得到什么?

成果 说明
docker-compose.vllm.yml vLLM 加 Qwen3-8B;只绑定 127.0.0.1:8000;必须设置 VLLM_API_KEY 才能启动;可以改用 ModelScope 下载
loadtest.py 预热一次 → 按不同并发数各发 n 个流式请求 → 输出吞吐和 TTFT 的中位数与 P95

需要准备哪些环境?

项目 要求 说明
GPU NVIDIA 显卡和 NVIDIA Container Toolkit 显存需求取决于模型、上下文长度和精度,以 Qwen 文档为准,先用小模型试
Docker 带 compose 插件 镜像 vllm/vllm-openai:latest,需要 ipc: host
Python 3.9 以上 pip install openai

文件结构

44-qwen-B-src/
├── docker-compose.vllm.yml
└── loadtest.py

第 1 步:部署

docker-compose.vllm.yml:

# vLLM 部署 Qwen3(OpenAI 兼容接口),需要 NVIDIA 显卡 + NVIDIA Container Toolkit。
# 参数来自 vLLM Docker 文档和 Qwen 官方 vLLM 部署文档:
#   --reasoning-parser qwen3                     思考内容单独放进 reasoning_content
#   --enable-auto-tool-choice --tool-call-parser hermes   工具调用
#   --api-key                                     给接口加一个密钥(vLLM 默认不鉴权)
# 国内下载慢可以改用 ModelScope:取消 VLLM_USE_MODELSCOPE 的注释
services:
  vllm:
    image: vllm/vllm-openai:latest
    ipc: host
    ports: ["127.0.0.1:8000:8000"]        # 只绑本机;要给局域网用,前面加反向代理和访问控制
    volumes:
      - ~/.cache/huggingface:/root/.cache/huggingface
    environment:
      HF_TOKEN: ${HF_TOKEN:-}
      # VLLM_USE_MODELSCOPE: "true"
    command: >
      --model Qwen/Qwen3-8B --served-model-name qwen3
      --max-model-len 32768 --gpu-memory-utilization 0.90
      --reasoning-parser qwen3 --enable-auto-tool-choice --tool-call-parser hermes
      --api-key ${VLLM_API_KEY:?请先设置 VLLM_API_KEY}
    deploy:
      resources:
        reservations:
          devices: [{driver: nvidia, count: all, capabilities: [gpu]}]
    restart: unless-stopped
$ export VLLM_API_KEY=$(openssl rand -hex 16)
$ docker compose -f docker-compose.vllm.yml up -d
$ curl -H "Authorization: Bearer $VLLM_API_KEY" http://127.0.0.1:8000/v1/models

不想要思考过程时,请求里加 "chat_template_kwargs": {"enable_thinking": false}(Qwen 文档)。

第 2 步:压测

loadtest.py:

"""对任意 OpenAI 兼容接口(vLLM / Ollama / 云 API)做并发压测:首字延迟(TTFT)和整体吞吐。
用法:python loadtest.py --base http://127.0.0.1:8000/v1 --model qwen3 --key $VLLM_API_KEY --conc 1 4 8 --n 8
关闭 Qwen3 思考:vLLM 用 --vllm-no-think(chat_template_kwargs.enable_thinking=false);Ollama 用 --ollama-no-think(reasoning_effort=none)
注意:在 Ollama 的 OpenAI 兼容接口上传 think=false 不起作用(实测仍然思考),要用 reasoning_effort=none"""
import argparse, statistics, time
from concurrent.futures import ThreadPoolExecutor
from openai import OpenAI

ap = argparse.ArgumentParser()
ap.add_argument("--base", required=True); ap.add_argument("--model", required=True); ap.add_argument("--key", default="EMPTY")
ap.add_argument("--conc", type=int, nargs="+", default=[1, 4]); ap.add_argument("--n", type=int, default=8)
ap.add_argument("--max-tokens", type=int, default=128)
ap.add_argument("--vllm-no-think", action="store_true"); ap.add_argument("--ollama-no-think", action="store_true")
a = ap.parse_args()
client = OpenAI(base_url=a.base, api_key=a.key, max_retries=0, timeout=600)
extra = {"chat_template_kwargs": {"enable_thinking": False}} if a.vllm_no_think else ({"reasoning_effort": "none"} if a.ollama_no_think else {})
PROMPTS = ["介绍一下杭州西湖", "用 Python 写冒泡排序", "解释 TCP 三次握手", "给新员工写一段欢迎词"]

def one(i):
    t0 = time.time(); ttft = None; toks = 0
    s = client.chat.completions.create(model=a.model, stream=True, max_tokens=a.max_tokens, temperature=0, extra_body=extra,
                                       stream_options={"include_usage": True},
                                       messages=[{"role": "user", "content": PROMPTS[i % len(PROMPTS)]}])
    for ch in s:
        if ch.choices and (ch.choices[0].delta.content or getattr(ch.choices[0].delta, "reasoning", None)
                           or getattr(ch.choices[0].delta, "reasoning_content", None)) and ttft is None: ttft = time.time() - t0
        if ch.usage: toks = ch.usage.completion_tokens
    return ttft or 0, toks, time.time() - t0

one(0)                                   # 预热:第一个请求包含加载模型的时间,不计入结果
for c in a.conc:
    t0 = time.time()
    with ThreadPoolExecutor(c) as ex: res = list(ex.map(one, range(a.n)))
    wall = time.time() - t0; ttfts = sorted(r[0] for r in res); total = sum(r[1] for r in res)
    p95 = ttfts[min(len(ttfts) - 1, int(len(ttfts) * 0.95))]
    print(f"并发 {c:>2}:{a.n} 个请求用时 {wall:5.1f}s,总吞吐 {total/wall:6.1f} tok/s,"
          f"首字延迟 中位 {statistics.median(ttfts):.2f}s / P95 {p95:.2f}s")
# box 上实测(Ollama,OLLAMA_NUM_PARALLEL=4)
$ python loadtest.py --base http://127.0.0.1:11434/v1 --model qwen3-local:0.6b --conc 1 2 4 --key x --n 8 --ollama-no-think
并发  1:8 个请求用时   9.7s,总吞吐  102.4 tok/s,首字延迟 中位 0.04s / P95 0.05s
并发  2:8 个请求用时   7.1s,总吞吐  139.0 tok/s,首字延迟 中位 0.06s / P95 0.08s
并发  4:8 个请求用时   4.3s,总吞吐  232.9 tok/s,首字延迟 中位 0.11s / P95 0.16s

对照实验:Ollama 在 OLLAMA_NUM_PARALLEL=1 时,吞吐基本不随并发增加,TTFT 在并发 4 时涨到 3.18 秒,因为请求在排队。vLLM 的连续批处理正是为解决这个问题设计的,但本文没有在 GPU 上实测。

调试示例:关不掉思考

压测 Ollama 时,我第一次通过 extra_body 传入 think: false。结果输出 tokens 很多,content 却是空的:一个请求返回了 1297 字的 reasoning,content 为空。原来 Ollama 的 OpenAI 兼容接口不认这个参数(它是 Ollama 原生 API 的参数)。改用 reasoning_effort: "none",或者在提示词末尾加 /no_think,都能生效。vLLM 上则用 chat_template_kwargs.enable_thinking=false。压测前一定要先看一眼实际输出,否则测出来的全是思考过程的速度。

常见问题

现象 原因 处理
容器启动后显存不足(OOM) 模型或上下文太大 降低 --max-model-len,或换小模型、量化版本
工具调用返回的是普通文本 没有开启工具调用解析 加上 --enable-auto-tool-choice --tool-call-parser hermes
401 没有带密钥 请求头加 Authorization: Bearer $VLLM_API_KEY
国内下载模型很慢 从 Hugging Face 下载 设置 VLLM_USE_MODELSCOPE=true

模板规则

  • 推理接口必须设置密钥,只绑定本机地址。
  • 压测前先预热,并确认输出的不是思考内容。
  • 配置以自己实测的数字为准。

项目资源

用途 地址
Qwen 文档:vLLM 部署 https://qwen.readthedocs.io/en/latest/deployment/vllm.html
vLLM Docker 部署 https://docs.vllm.ai/en/latest/deployment/docker.html
Qwen3-8B 模型卡片 https://huggingface.co/Qwen/Qwen3-8B
Ollama 环境变量(FAQ) https://docs.ollama.com/faq

先测出 TTFT 和吞吐,再谈要多少张卡、开多大并发。