在双 H100 上用 vLLM 部署 DeepSeek‑V4‑Flash(1M 上下文)+ 编程 Agent 实战
本文记录在一台 2× NVIDIA H100 NVL(每卡约 96GB,合计 ~192GB 显存) 的服务器上,用 vLLM 部署 DeepSeek‑V4‑Flash(原生 1M 上下文)的完整过程:环境搭建、模型下载、服务化、性能优化,以及把 codex / qwen‑code / opencode 三个命令行编程 Agent 接到这个本地端点上的配置与压测。重点是我踩过的几个坑——尤其是一个让推理速度慢了 13 倍 的低级失误。
0. TL;DR(先看结论)
- DeepSeek‑V4‑Flash 用 MLA(多头潜在注意力),KV cache 极省,1M 上下文 + CUDA graphs 可以同时成立。
- 千万别图省事加
--enforce-eager:它会关掉 CUDA graphs,把解码从 ~67 tok/s 打成 ~5 tok/s。 - agentic CLI(codex/qwen‑code/opencode)需要服务端开
--enable-auto-tool-choice --tool-call-parser deepseek_v4,否则直接 HTTP 400。 - codex 0.142+ 必须用
wire_api = "responses"(不再支持chat),好在 vLLM 暴露了/v1/responses。 - Agent 与模型服务尽量同机部署:流式输出是"延迟敏感型",隔着网络会明显变慢。
最终稳定配置:1M 上下文 + FP8 KV + CUDA graphs + 工具调用,解码 ~67 tok/s,显存 ~91GB/卡。
1. 硬件与软件环境
| 项目 | 版本 / 规格 |
|---|---|
| GPU | 2× NVIDIA H100 NVL(~96GB/卡,合计 ~192GB) |
| OS | Ubuntu 24.04 LTS |
| NVIDIA 驱动 | 580.x |
| CUDA | 13.0(/usr/local/cuda-13.0) |
| vLLM | 0.23.0 |
| PyTorch | 2.11.0 + cu130 |
| transformers | 5.12.1 |
| flashinfer | 0.6.12 |
| huggingface‑hub | 1.21.x |
经验:为不同模型族准备相互独立的 venv。例如旧的 Qwen 系列用 vLLM 0.21,新的 DeepSeek‑V4 需要 vLLM 0.23——并存、互不干扰,升级一个不会搞坏另一个。
2. 搭建 vLLM 环境(独立 venv)
# 为新模型单独建一个 venv,避免污染已有环境
python3 -m venv /opt/vllm-env-next
/opt/vllm-env-next/bin/pip install --upgrade pip
/opt/vllm-env-next/bin/pip install vllm==0.23.0
坑 1:用户级 site‑packages 污染。 如果机器上别处装过别的 torch,运行时容易串库。统一在启动脚本里:
unset PYTHONPATH export PYTHONNOUSERSITE=1这样强制只用当前 venv 的包。
CUDA 工具链也要对齐(某些 FP8/FP4 kernel 的 JIT 编译需要较新的 nvcc):
export CUDA_HOME=/usr/local/cuda-13.0
export PATH=$CUDA_HOME/bin:$PATH
export LD_LIBRARY_PATH=$CUDA_HOME/lib64:${LD_LIBRARY_PATH:-}
3. 下载模型
DeepSeek‑V4‑Flash 是 FP8 量化(quantization = deepseek_v4_fp8),磁盘约 149GB,架构 DeepseekV4ForCausalLM,max_position_embeddings = 1048576(原生 1M)。
# 注意:新版本里 huggingface-cli 已弃用,改用 hf
export HF_HUB_DISABLE_XET=1 # 关键:见下方坑 2
/opt/vllm-env-next/bin/hf download <org>/DeepSeek-V4-Flash \
--local-dir /data/models/DeepSeek-V4-Flash
坑 2:Xet 高速传输在未登录时会"假死"。 用默认的 Xet 通道下载大模型,未带 HF token 时常常下到几十 GB 就卡死不动(进程还在,但字节数一动不动)。表现极具迷惑性。 解决:
export HF_HUB_DISABLE_XET=1退回标准 HTTPS(支持断点续传、稳定),或配置HF_TOKEN提高额度。再写个while ... hf download ... && break的重试循环更稳。
坑 3:
huggingface-cli download已经不工作了。 新版huggingface_hub里它只打印一段"请改用hf"的提示就退出,不会真的下载。一定要用hf download。
4. 启动脚本与 systemd 服务
4.1 启动脚本 start-vllm-dsv4.sh
#!/usr/bin/env bash
# vLLM OpenAI 服务:DeepSeek-V4-Flash(MLA + FP8 权重 / FP4 专家)
set -euo pipefail
unset PYTHONPATH
export PYTHONNOUSERSITE=1
VENV=/opt/vllm-env-next
MODEL_PATH=/data/models/DeepSeek-V4-Flash
export HF_HOME=/data/huggingface
export HF_HUB_OFFLINE=1 # 权重已在本地,启动时不必联网
export VLLM_CACHE_ROOT=$HOME/.cache/vllm
export CUDA_VISIBLE_DEVICES=0,1
export CUDA_HOME=/usr/local/cuda-13.0
export PATH=$CUDA_HOME/bin:$PATH
export LD_LIBRARY_PATH=$CUDA_HOME/lib64:${LD_LIBRARY_PATH:-}
# 这一组是针对该模型 FP8/FP4 kernel + 当前驱动/CUDA 组合调出来的,
# 属于"模型 + 硬件相关"的开关,换模型/换卡可能需要重新验证。
export VLLM_BLOCKSCALE_FP8_GEMM_FLASHINFER=0
export VLLM_USE_DEEP_GEMM=1
export VLLM_USE_DEEP_GEMM_E8M0=1
export VLLM_USE_FLASHINFER_MOE_FP4=1
export VLLM_MXFP4_USE_MARLIN=1
MAXLEN=${MAXLEN:-1048576} # 1M 上下文;OOM 时调小
exec "$VENV/bin/vllm" serve "$MODEL_PATH" \
--host 0.0.0.0 --port 8000 \
--tensor-parallel-size 2 \
--max-model-len "$MAXLEN" \
--kv-cache-dtype fp8 \
--gpu-memory-utilization 0.95 \
--max-num-seqs 16 \
--trust-remote-code \
--enable-auto-tool-choice \
--tool-call-parser deepseek_v4 \
--served-model-name DeepSeek-V4-Flash
4.2 systemd 服务 vllm-dsv4.service
[Unit]
Description=vLLM OpenAI server (DeepSeek-V4-Flash, 1M ctx, 2x H100)
After=network-online.target
Wants=network-online.target
# 若同机上有多个互斥的模型服务,用 Conflicts 防止它们同时抢 GPU/端口
# Conflicts=other-vllm.service
[Service]
Type=simple
User=youruser
WorkingDirectory=/data
ExecStart=/data/start-vllm-dsv4.sh
StandardOutput=append:/data/vllm-dsv4.log
StandardError=append:/data/vllm-dsv4.log
Restart=on-failure
RestartSec=15
TimeoutStartSec=1800 # 1M 上下文 + 149GB 权重,启动给足时间
LimitNOFILE=1048576
LimitMEMLOCK=infinity
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now vllm-dsv4.service
5. 性能优化:最重要的一课
5.1 --enforce-eager 是个大坑
一开始为了"稳",我在启动参数里带了 --enforce-eager。结果解码只有 ~5 tok/s,任何稍长的回答都慢到像卡死。
--enforce-eager 会关闭 CUDA graphs(以及 torch.compile)。对 DeepSeek‑V4 这种大 MoE,没有 CUDA graphs,每步解码都被 kernel 启动开销和 Python 逐层调度拖垮。
之所以当初敢加,是潜意识里以为"1M 上下文太吃显存,CUDA graphs 放不下"。这个假设是错的:DeepSeek‑V4 用 MLA,KV cache 极其紧凑,1M 上下文的 KV 池才占 ~10GB/卡,完全留得出空间给 CUDA graphs。
去掉 --enforce-eager 后:
| 配置 | 解码速度 |
|---|---|
带 --enforce-eager |
~5 tok/s |
| 去掉(开启 CUDA graphs) | ~67 tok/s(约 13×) |
5.2 上下文长度 vs 并发:两个独立的上限
可用上下文 = min( 模型自身上限 , 显存里 KV 池能装下的 token 数 )
- 模型上限:由训练时的
max_position_embeddings决定(这里是 1,048,576)。超过它要靠 YaRN 外推,质量会掉。 - 显存上限:每个 token 占一份 KV cache;可用 KV 显存决定所有并发请求共享的总 token 容量。
vLLM 启动日志会直接告诉你这两个数,例如:
GPU KV cache size: 1,491,011 tokens
Maximum concurrency for 1,048,576 tokens per request: 1.42x
含义:KV 池能装约 149 万 token;一条 1M 的请求会吃掉其中约 70%,所以"满长请求"的并发约 1.42 路。--max-model-len 只是设"单条最长",它本身不额外占显存——显存在 token 真正进来时才按量消耗。
实测在本机的几档:
| max-model-len | KV 池 | 满长并发 | 解码速度 |
|---|---|---|---|
| 256K | ~70 万 token | 2.66× | 64.7 tok/s |
| 1M(原生) | ~149 万 token | 1.42× | 66.9 tok/s |
因为 MLA 的 KV 太省,直接拉满原生 1M 也几乎不影响速度,显存约 91GB/卡。
5.3 其它优化点
--kv-cache-dtype fp8:KV 再砍一半,等于把可用上下文/并发翻倍。--gpu-memory-utilization 0.95:在不 OOM 的前提下尽量榨干显存给 KV 池。--max-num-seqs:按你的并发需求设,单人交互 8–16 足够。
6. 踩坑汇总(speedrun)
--enforce-eager→ 慢 13 倍。 见上,DeepSeek‑V4(MLA)1M + CUDA graphs 可兼得。- 缺工具调用参数 → agentic CLI 报 400。 必须加
--enable-auto-tool-choice --tool-call-parser deepseek_v4(vLLM 0.23 自带这个 parser)。否则 qwen‑code 等会直接400 "auto" tool choice requires --enable-auto-tool-choice and --tool-call-parser。 - 重启时 GPU 显存释放有竞态。 旧实例还没退干净就
start,新实例做显存探测会报ValueError: Free memory ... less than desired。正确姿势:先stop,轮询nvidia-smi等显存降到接近 0,再start。 - 多进程 worker 不随主进程一起死。 手动起的 vLLM,有时杀了主进程,
VLLM::Worker_TP*还占着几十 GB 显存。用进程组或按确切 PID 收尾,并核对nvidia-smi --query-compute-apps。 - 重复/残留的 systemd 服务在抢资源。 我曾有个
enabled且Restart=on-failure的重复服务,因端口被占不断崩溃→30 秒后又被拉起,反复抢 GPU。排查:systemctl list-unit-files | grep -i vllm,把多余的disable --now。 - HF 下载:用
hf不要用huggingface-cli;未登录的 Xet 会卡死 →HF_HUB_DISABLE_XET=1。 codex exec通过 SSH 非交互执行会卡在读 stdin → 加< /dev/null。
7. 部署编程 Agent
三个 Agent 都走 OpenAI 兼容协议 接到本地 http://localhost:8000/v1。强烈建议把 Agent 和模型服务装在同一台机器上(原因见第 8 节)。
7.1 codex(OpenAI Codex CLI)
npm install -g @openai/codex
~/.codex/config.toml:
model = "DeepSeek-V4-Flash"
model_provider = "vllm_local"
model_context_window = 1000000
[model_providers.vllm_local]
name = "Local vLLM DeepSeek-V4-Flash"
base_url = "http://localhost:8000/v1"
env_key = "OPENAI_API_KEY" # codex 从该环境变量读 key
wire_api = "responses" # 关键:见下
export OPENAI_API_KEY=sk-local-anything # 本地服务不校验,但 codex 要求非空
坑 8:codex 0.142+ 不再支持
wire_api = "chat",会直接报错要你改成"responses"。所幸 vLLM 暴露了 OpenAI Responses API(/v1/responses),所以wire_api = "responses"能正常对接。另:codex 对未知模型会提示
Model metadata not found,无害;可用model_context_window告诉它上下文窗口。
7.2 qwen‑code
npm install -g @qwen-code/qwen-code
~/.qwen/.env:
OPENAI_API_KEY=sk-local-anything
OPENAI_BASE_URL=http://localhost:8000/v1
OPENAI_MODEL=DeepSeek-V4-Flash
qwen‑code 直接读这几个环境变量,配置最简单。
7.3 opencode
# 官方安装脚本(或 npm)
curl -fsSL https://opencode.ai/install | bash
opencode upgrade # 升级到最新
~/.config/opencode/opencode.json(用社区的 @ai-sdk/openai-compatible provider):
{
"$schema": "https://opencode.ai/config.json",
"model": "local-vllm/deepseek-v4",
"permission": { "*": "allow" },
"provider": {
"local-vllm": {
"npm": "@ai-sdk/openai-compatible",
"name": "Local vLLM (DeepSeek-V4-Flash)",
"options": {
"baseURL": "http://127.0.0.1:8000/v1",
"timeout": 600000
},
"models": {
"deepseek-v4": { "id": "DeepSeek-V4-Flash", "name": "DeepSeek-V4-Flash (Local)" }
}
}
}
}
opencode 还可挂 LSP 和 MCP(filesystem / memory / sequential‑thinking 等)。功能强,但这些 MCP server 会拉高启动开销,首次响应更慢。
8. 性能测试
同一道题(What is 7 times 8?),在模型同机本地实测:
| Agent | 版本 | 端到端用时 | 备注 |
|---|---|---|---|
| codex | 0.142.x | ~1.5s | 最快;首请求会预填 ~11K token 的系统提示 |
| qwen‑code | 0.15.x | ~5s | 配置最简单 |
| opencode | 1.17.x | ~16s | 慢在启动多个 MCP server,模型本身很快 |
服务端纯解码 66.9 tok/s(在线日志里 generation throughput 可印证)。
为什么"同机部署"很重要:流式是延迟敏感型
一个反直觉的发现:LLM 的流式(SSE 逐 token)输出,瓶颈不是带宽,而是延迟。
- 模型每生成一个 token 就 flush 一个很小的数据块;一次回答就是成百上千个小消息。
- 在
localhost,每个小块延迟≈0,所以能跑满 ~67 tok/s。 - 一旦隔着网络代理/远程通道,每个 token 小包都要付一整次往返延迟 + TLS/帧封装开销,流式速度会明显下降(我实测从 ~67 掉到 ~27 tok/s),而一次性(非流式)的大请求几乎不受影响——因为它能把延迟摊薄。
类比:本机直连像"一封信塞 200 页一次寄到";隔网络流式像"拆成 200 张明信片,一张一张寄、还要等到了再寄下一张"——纸张总量(带宽)不是问题,来回跑腿(延迟)才是。
结论:把编程 Agent 直接装在 GPU 主机上、连 localhost,是体验最好的方案;延迟敏感的流式负载尤其如此。
9. 经验总结
- MLA 模型(DeepSeek‑V4)KV 极省:别被"1M 上下文"吓到,CUDA graphs 完全放得下——不要无脑加
--enforce-eager。 - agentic CLI 需要服务端开工具调用:
--enable-auto-tool-choice --tool-call-parser <model>,这是"Agent 用不了"的头号原因,比速度问题更隐蔽。 - 重启 vLLM 要先确认 GPU 显存释放干净,并清理残留 worker / 重复服务。
- codex 用
wire_api = "responses";qwen‑code 走环境变量;opencode 用 openai‑compatible provider。 - 流式看延迟、批量看带宽:Agent 与模型同机,省心又快。
把上面这套配好,你就能在两张 H100 上跑一个 1M 上下文、~67 tok/s、支持工具调用 的本地 DeepSeek‑V4‑Flash,并用 codex / qwen‑code / opencode 把它当编程助手用。Happy hacking!
本文所有命令均为示意,实际路径/账号请按你自己的环境替换。模型与硬件相关的环境变量开关,换型号或换卡后建议重新验证。
更多推荐



所有评论(0)