ARTICLE · 1158257
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 编号只是同机部署示例。只有一张卡时,可以分别验证两个 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. 常见报错
| 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,再迁移参数。文末“阅读原文”可打开官网版本,查看和复制命令。