夜雨聆风学习资料网

ARTICLE · 1158257

vLLM 私有化大模型部署实战指南:离线安装、接口调用与压测调优

vLLM 私有化大模型部署实战指南:离线安装、接口调用与压测调优

vLLM 私有化大模型部署实战指南:离线安装、接口调用与压测调优

最近,不少档案行业的同仁问我,大模型怎么部署到自己的服务器上,离线环境下又该怎么用。于是整理了这份 vLLM 部署指南,把准备、安装和验证的步骤写清楚,供大家动手尝试。

下面给出三个模型的离线部署步骤:DeepSeek-V4-Flash-0731 用于对话生成,Qwen3-Embedding-8B 用于文本向量化,Qwen3.5-9B 用于较小规模的对话服务。可以分别部署,也可以在不同 GPU 上同时运行。

命令按 2026 年 10 月 11 日可查的 vLLM v0.31.0、官方模型配置及部署说明核对,尚未在对应 GPU 服务器上实跑,不附性能成绩。示例先验证文本接口;Qwen3.5 的图片和视频输入不在本次验证范围内。

1. 准备服务器和运行环境

使用 Linux、Docker、NVIDIA 驱动及 NVIDIA Container Toolkit。联网准备机与内网服务器采用相同 CPU 架构;以下镜像按 Linux x86_64 准备。

模型
示例 GPU 分配
本机端口
服务别名
DeepSeek-V4-Flash-0731
0、1、2、3
8000
deepseek-v4-flash
Qwen3-Embedding-8B
4
8001
qwen3-embedding
Qwen3.5-9B
5
8003
qwen35-9b

GPU 编号只是同机部署示例。只有一张卡时,可以分别验证两个 Qwen 模型,启动前改成 device=0,并停止另一个占卡服务。

DeepSeek 这份权重约 166.9GB,运行还需要额外显存。下面采用面向 Blackwell 的四卡专家并行配置;官方模型说明提供的是 4×GB300 示例,vLLM recipe 另有 B300 配置。x86_64 服务器应选择对应架构的 B300 等受支持设备,不能拿四张 24GB 卡照搬。GB300 的 Grace 主机需另备 arm64 镜像。

两个 Qwen 模型按 BF16 单卡加载,建议先用 32GB 或更大显存验证。24GB 卡应缩短上下文、降低并发,并以启动日志中的可用缓存为准。驱动需满足镜像 CUDA 版本要求。

在目标服务器检查:

BASH

1nvidia-smi

2docker version

3nvidia-ctk --version

本文使用 vllm/vllm-openai:v0.31.0,其默认 CUDA 为 13.0。若使用其他 GPU 或 CUDA 变体,需要重新核对算子支持,不能只改镜像标签。

2. 在联网机器准备离线包

固定镜像版本,保存镜像 ID 和 digest:

BASH

1mkdir -p bundle/models

2VLLM_IMAGE=vllm/vllm-openai:v0.31.0

3

4docker pull --platform linux/amd64 "$VLLM_IMAGE"

5docker image inspect "$VLLM_IMAGE" \

6  --format '{{.Id}}' > bundle/image-id.txt

7docker image inspect "$VLLM_IMAGE" \

8  --format '{{json .RepoDigests}}' > bundle/image-repodigests.json

9docker save -o bundle/vllm-image.tar "$VLLM_IMAGE"

下载完整模型仓库,并把下载时的 main 固定成具体 commit。磁盘需同时容纳模型、镜像导出文件及导入后的镜像;不需要的模型可以从列表中删去。

BASH

1python3 -m venv .download-venv

2.download-venv/bin/pip install 'huggingface_hub==0.34.4'

3

4.download-venv/bin/python - <<'PY'

5import json

6from pathlib import Path

7from huggingface_hub import model_info, snapshot_download

8

9repos = [

10    "deepseek-ai/DeepSeek-V4-Flash-0731",

11    "Qwen/Qwen3-Embedding-8B",

12    "Qwen/Qwen3.5-9B",

13]

14revisions = {}

15for repo in repos:

16    revision = model_info(repo, revision="main").sha

17    snapshot_download(

18        repo_id=repo,

19        revision=revision,

20        local_dir=f"bundle/models/{repo.rsplit('/', 1)[-1]}",

21    )

22    revisions[repo] = revision

23Path("bundle/model-revisions.json").write_text(

24    json.dumps(revisions, indent=2) + "\n"

25)

26PY

保留权重、分片索引、配置、tokenizer 和仓库辅助代码。DeepSeek 命令启用了 --trust-remote-code,相关代码应在联网准备阶段审查。驱动、Docker 和 Container Toolkit 的离线安装包也需提前准备,它们不包含在模型镜像里。

在 Linux 准备机生成 SHA256 清单:

BASH

1cd bundle

2find . -type f ! -path './SHA256SUMS' -print0 \

3  | sort -z \

4  | xargs -0 sha256sum > SHA256SUMS

5sha256sum -c SHA256SUMS

如果模型需要额外依赖,应在外网构建、验证并导出新镜像,再重新保存镜像 ID。不要等到内网启动时报缺包再临时联网安装。

3. 在内网导入并检查 GPU

将整个 bundle 转移到 /opt/vllm/bundle,执行:

BASH

1cd /opt/vllm/bundle

2sha256sum -c SHA256SUMS

3docker load -i vllm-image.tar

4VLLM_IMAGE_ID=$(cat image-id.txt)

5

6docker run --rm --pull=never --gpus device=0 \

7  --entrypoint python "$VLLM_IMAGE_ID" \

8  -c 'import torch; x=torch.ones(1,device="cuda"); print(torch.cuda.get_device_name(0)); print(x.cpu())'

张量检查失败时,先排查宿主机驱动和容器运行时。多卡部署还需检查 GPU 拓扑及卡间通信:

BASH

1nvidia-smi topo -m

创建运行配置。首次执行生成 key;已有文件时保留原 key:

BASH

1cd /opt/vllm

2mkdir -p results

3umask 077

4if [ ! -e runtime.env ]; then

5  {

6    printf 'VLLM_API_KEY=%s\n' "$(openssl rand -hex 32)"

7    printf '%s\n' \

8'HF_HUB_OFFLINE=1' \

9'TRANSFORMERS_OFFLINE=1' \

10'HF_HUB_DISABLE_TELEMETRY=1' \

11'VLLM_NO_USAGE_STATS=1'

12  } > runtime.env

13fi

14VLLM_IMAGE_ID=$(cat bundle/image-id.txt)

这几个离线变量控制相关库的行为;服务器禁止外联还需实际网络隔离。以下端口均绑定宿主机 127.0.0.1,供本机验证。

4. 启动 DeepSeek-V4-Flash-0731

此命令面向前述 Blackwell 四卡环境,采用 DP4 加专家并行。先用 8192 总上下文、每个调度器最多 2 条序列验证,暂不开推测解码。

BASH

1cd /opt/vllm

2VLLM_IMAGE_ID=$(cat bundle/image-id.txt)

3

4docker run -d --name vllm-deepseek \

5  --pull=never --gpus '"device=0,1,2,3"' \

6  --shm-size=16g \

7  --env-file /opt/vllm/runtime.env \

8  -p 127.0.0.1:8000:8000 \

9  -v /opt/vllm/bundle/models/DeepSeek-V4-Flash-0731:/model:ro \

10  -v /opt/vllm/results:/bench \

11  --entrypoint vllm \

12  "$VLLM_IMAGE_ID" serve /model \

13  --served-model-name deepseek-v4-flash \

14  --host 0.0.0.0 --port 8000 \

15  --trust-remote-code \

16  --data-parallel-size 4 --enable-expert-parallel \

17  --moe-backend deep_gemm_mega_moe \

18  --kv-cache-dtype fp8 --block-size 256 \

19  --attention-config '{"use_fp4_indexer_cache":true}' \

20  --tokenizer-mode deepseek_v4 \

21  --reasoning-parser deepseek_v4 \

22  --max-model-len 8192 --max-num-seqs 2 \

23  --gpu-memory-utilization 0.90 \

24  --generation-config vllm \

25  --no-enable-log-requests

deep_gemm_mega_moe 和 FP4 indexer cache 有硬件条件;H200、RTX PRO 6000 等设备应使用各自的官方 recipe。0731 检查点的可选推测解码是 DSpark,不要套用预览版的 MTP 命令。基础服务通过后再单独测试优化项。

5. 启动 Qwen3-Embedding-8B

向量模型使用 pooling runner,接口为 /v1/embeddings。容器内部仍监听 8000,映射到宿主机 8001。

BASH

1cd /opt/vllm

2VLLM_IMAGE_ID=$(cat bundle/image-id.txt)

3

4docker run -d --name vllm-embedding \

5  --pull=never --gpus device=4 --shm-size=8g \

6  --env-file /opt/vllm/runtime.env \

7  -p 127.0.0.1:8001:8000 \

8  -v /opt/vllm/bundle/models/Qwen3-Embedding-8B:/model:ro \

9  --entrypoint vllm \

10  "$VLLM_IMAGE_ID" serve /model \

11  --served-model-name qwen3-embedding \

12  --runner pooling --dtype bfloat16 \

13  --host 0.0.0.0 --port 8000 \

14  --max-model-len 4096 --max-num-seqs 2 \

15  --gpu-memory-utilization 0.90 \

16  --no-enable-log-requests

6. 启动 Qwen3.5-9B

这里仅验证文字对话,用 --language-model-only 跳过视觉编码器。需要图像输入时,应另行配置并验证多模态服务。

BASH

1cd /opt/vllm

2VLLM_IMAGE_ID=$(cat bundle/image-id.txt)

3

4docker run -d --name vllm-qwen35 \

5  --pull=never --gpus device=5 --shm-size=8g \

6  --env-file /opt/vllm/runtime.env \

7  -p 127.0.0.1:8003:8000 \

8  -v /opt/vllm/bundle/models/Qwen3.5-9B:/model:ro \

9  -v /opt/vllm/results:/bench \

10  --entrypoint vllm \

11  "$VLLM_IMAGE_ID" serve /model \

12  --served-model-name qwen35-9b \

13  --language-model-only --dtype bfloat16 \

14  --reasoning-parser qwen3 \

15  --host 0.0.0.0 --port 8000 \

16  --max-model-len 4096 --max-num-seqs 2 \

17  --gpu-memory-utilization 0.90 \

18  --generation-config vllm \

19  --no-enable-log-requests

7. 检查就绪状态和模型名

首次启动要加载权重并准备算子,容器显示 running 时模型可能还没就绪。先看日志:

BASH

1docker logs --tail 100 vllm-deepseek

2docker logs --tail 100 vllm-embedding

3docker logs --tail 100 vllm-qwen35

在推理服务器本机加载 key,检查三个端口。只部署了其中一个时,修改端口列表:

BASH

1cd /opt/vllm

2set -a

3. ./runtime.env

4set +a

5

6for port in 8000 8001 8003; do

7  curl --fail --max-time 10 "http://127.0.0.1:$port/health" || exit 1

8  curl --fail --max-time 10 \

9    -H "Authorization: Bearer $VLLM_API_KEY" \

10    "http://127.0.0.1:$port/v1/models" || exit 1

11done

/v1/models 中的 id 应分别对应表中的服务别名。连接失败时查看日志,等待启动完成后重新执行。

8. 验证两个对话接口

DeepSeek 请求:

BASH

1curl --fail --max-time 180 \

2  http://127.0.0.1:8000/v1/chat/completions \

3  -H "Authorization: Bearer $VLLM_API_KEY" \

4  -H 'Content-Type: application/json' \

5  -d '{

6    "model":"deepseek-v4-flash",

7    "messages":[{"role":"user","content":"17乘以19等于多少?只输出数字。"}],

8    "temperature":0,

9    "max_tokens":256

10  }' -o /opt/vllm/results/deepseek-chat.json

Qwen3.5 请求关闭思考模式,便于检查短答案:

BASH

1curl --fail --max-time 180 \

2  http://127.0.0.1:8003/v1/chat/completions \

3  -H "Authorization: Bearer $VLLM_API_KEY" \

4  -H 'Content-Type: application/json' \

5  -d '{

6    "model":"qwen35-9b",

7    "messages":[{"role":"user","content":"17乘以19等于多少?只输出数字。"}],

8    "chat_template_kwargs":{"enable_thinking":false},

9    "temperature":0,

10    "max_tokens":256

11  }' -o /opt/vllm/results/qwen35-chat.json

读取结果并检查:

BASH

1python3 - <<'PY'

2import json

3from pathlib import Path

4

5for name in ["deepseek-chat.json", "qwen35-chat.json"]:

6    data = json.loads((Path("/opt/vllm/results") / name).read_text())

7    choice = data["choices"][0]

8    answer = choice["message"].get("content") or ""

9    print(name, repr(answer), choice["finish_reason"], data.get("usage"))

10    assert answer.strip() == "323", (name, answer)

11    assert choice["finish_reason"] == "stop", choice["finish_reason"]

12PY

这一步只检查接口、短答案及结束状态。若 finish_reason 为 length,查看输出是否被截断,再调整输出上限或思考设置。

9. 验证向量接口

Qwen3-Embedding 的查询文本建议带任务指令,待检索文档直接传正文。下面一次传入一个查询、两个候选文本:

BASH

1curl --fail --max-time 180 \

2  http://127.0.0.1:8001/v1/embeddings \

3  -H "Authorization: Bearer $VLLM_API_KEY" \

4  -H 'Content-Type: application/json' \

5  -d '{

6    "model":"qwen3-embedding",

7    "input":[

8      "Instruct: Given a web search query, retrieve relevant passages that answer the query\nQuery: 中国的首都是哪里?",

9      "中国的首都是北京。",

10      "水在标准大气压下的沸点是100摄氏度。"

11    ],

12    "encoding_format":"float"

13  }' -o /opt/vllm/results/embeddings.json

14

15python3 - <<'PY'

16import json

17import math

18from pathlib import Path

19

20data = json.loads(Path("/opt/vllm/results/embeddings.json").read_text())

21rows = sorted(data["data"], key=lambda row: row["index"])

22assert [row["index"] for row in rows] == [0, 1, 2]

23vectors = [row["embedding"] for row in rows]

24assert all(len(v) == 4096 for v in vectors)

25assert all(math.isfinite(x) for v in vectors for x in v)

26norms = [math.sqrt(sum(x*x for x in v)) for v in vectors]

27assert all(n > 0 for n in norms)

28for i in [1, 2]:

29    score = sum(a*b for a, b in zip(vectors[0], vectors[i]))

30    score /= norms[0] * norms[i]

31    print("候选", i, "余弦相似度", round(score, 4))

32print("向量数量、维度和数值检查通过")

33PY

默认向量维度为 4096。这个小样例用来确认输出格式和基本语义表现,不代表真实资料上的检索准确率。

10. 做一轮基础压测

以下测试 Qwen3.5 的文字接口,输入约 1024 token、输出目标 256 token、100 个请求、最大并发 2。压测使用容器内的本地 tokenizer,不再下载模型。

BASH

1docker exec -e OPENAI_API_KEY="$VLLM_API_KEY" \

2  vllm-qwen35 vllm bench serve \

3  --backend openai-chat \

4  --base-url http://127.0.0.1:8000 \

5  --endpoint /v1/chat/completions \

6  --model /model --served-model-name qwen35-9b \

7  --tokenizer /model \

8  --dataset-name random \

9  --random-input-len 1024 --random-output-len 256 \

10  --num-prompts 100 --max-concurrency 2 \

11  --request-rate inf --ignore-eos --seed 42 \

12  --extra-body '{"chat_template_kwargs":{"enable_thinking":false}}' \

13  --percentile-metrics ttft,tpot,itl,e2el \

14  --metric-percentiles 50,95,99 \

15  --save-result --result-dir /bench

先确认成功请求数等于提交数,再看首 token 延迟 TTFT、每输出 token 时间 TPOT、总延迟及输出吞吐量。--ignore-eos 用于合成长输出负载,不代表日常对话行为。

测试 DeepSeek 时,将容器名换为 vllm-deepseek、服务名换为 deepseek-v4-flash,并删除 Qwen 专用的 --extra-body 行。向量接口需单独测试,不能拿上述生成 token 指标衡量。

并发从 2 开始逐步增加,每轮保持输入输出长度一致。同时记录 GPU 型号、驱动、镜像 ID、模型 commit 和完整命令,避免把不同条件下的结果放在一起比较。

11. 常见报错

现象
优先检查
容器无法使用 GPU
宿主机 nvidia-smi、Container Toolkit、容器张量检查
CUDA 或算子不支持
GPU 架构、驱动、镜像 CUDA、模型专用内核要求
尝试连接外网
是否挂载完整模型目录、参数是否使用 /model、有无缺失依赖
显存不足
权重能否装下、其他 GPU 进程、上下文和并发设置
DeepSeek 多卡启动挂起
GPU 拓扑、NCCL 通信、共享内存及各 worker 日志
401 或模型不存在
key、宿主机端口、/v1/models 返回的服务别名
对话模板报错
tokenizer 文件、模型专用 tokenizer mode、框架版本
向量接口报任务不匹配
是否部署 Embedding 模型、是否使用 pooling runner
修改参数后无变化
docker restart
 保留原参数;保存日志后重建对应容器

需要换启动参数时,先保留旧容器,示例:

BASH

1docker logs vllm-qwen35 > /opt/vllm/results/qwen35-before-change.log

2docker stop vllm-qwen35

3docker rename vllm-qwen35 "vllm-qwen35-backup-$(date +%Y%m%d%H%M%S)"

再用新参数执行第 6 步,重新检查接口与压测结果。只修改自己的目标容器,不批量删除其他服务。

版本更换后,先在新镜像中检查 vllm serve --help 和 vllm bench serve --help,再迁移参数。文末“阅读原文”可打开官网版本,查看和复制命令。

相关学习资料