跳至正文
全部文章

在 H20 上部署 DeepSeek-V4.1-Flash

一台 8 卡 H20 跑 DeepSeek-V4.1-Flash 的部署经验:上下文长度怎么定、为什么用 vLLM 而不是 SGLang、调参的结论、怎么压测,以及上线和回滚的思路。

约 11 分钟#大模型部署#vLLM#SGLang#H20

这篇记录 DeepSeek-V4.1-Flash 在一台 8 卡 H20 上的部署:上下文为什么开到 512K,为什么选 vLLM 不选 SGLang,调参得到哪些结论,压测怎么做,以及上线和回滚的思路。数字来自 2026 年 9 月到 10 月的测试记录,每个数字都写明口径。没测过的地方,我直接写没测。

短请求 · 并发 32

997tok/s
上升+10%较原生产
输入 256,输出 128

1K/1K · 并发 100

1,692tok/s
上升+26%较原生产
输入 1K,输出 1K

长输入 · 并发 10

390tok/s
上升+80%较原生产
输入 17.4K,输出 1K

上下文上限

512Ktoken
524,288,含提示词与输出

目标与约束

这次部署的是 V4.1-Flash 的 1017 版权重(475.3 GiB,48 个 shard),替换同一台机器上已经在跑的 0909 版。目标有三条:对外服务名不变,调用方无感切换;上下文要能到 512K;相对已有基线,目标并发的输出吞吐下降不超过 10%,TTFT P50 劣化不超过 10%,P95 不超过 15%,成功率 100%。最后一条是评估方案里写死的门槛,后面所有“淘汰”都按它判。

机器是 8 张 H20(SM90),每卡约 97.9 GiB。有四条约束直接决定了后面的选择:

  • 一个实例要占满 8 卡。试过两个 TP4 实例各占 4 卡,能启动,但单实例的 KV cache 只有 1,615,139 token,512K 下满长请求只能并发 3.08 路;TP8 加 EP8 是 17,594,168 token,33.56 路。
  • FP4 的 indexer 缓存用不了。vLLM 把这条路径解析成 MXFP4,要求 Blackwell,H20 启动直接失败,只能用 FP8。
  • 经典 MTP 用不了,V4.1 的权重里没有它需要的部分,投机解码只能用 DSpark。
  • 评估新版本必须停掉线上容器,所以每次评估都要先让网关把流量切走。

上下文长度怎么取舍

我手上没有线上请求长度的分布统计,所以上限不是按分布定的,是按两个问题定的:显存能撑多少路,长请求要付多大代价。

先看容量。同一镜像、同一组参数,只改上下文上限和 Engram 表的位置,vLLM 启动时报告的 KV 容量折算成满长并发如下。Engram 表放 GPU 每卡多占 23.61 GiB(8 卡约 189 GiB),256K 下 KV 容量只剩主机内存方案的约 41%。多花的显存没换来速度:并发 1 到 16 的吞吐差别在 1% 以内,240K 输入的首 token 是 56.1 秒对 56.6 秒。所以 Engram 表一直放主机内存。

满长请求能并发几路

KV 容量除以上限,vLLM 启动时的估计值,不是压测结果

  • Engram 在主机内存
  • Engram 在 GPU
配置KV cache(token)满长并发
256K · 主机内存15,557,83359.35
256K · GPU6,393,94024.39
1M · 主机内存16,510,09115.75
1M · GPU4,237,3864.04

1M 能跑:两种放置方式都完成了一个 1,000,000 token 输入、16 token 输出的请求,但首 token 要 460 秒左右,满长并发也只有 15.75 路,我没有把它当上线选项。

最终取 524288,对应 17,594,168 token 的 KV 容量,满长并发 33.56 路。这个长度上,1017 版换上 vLLM v0.32.0 镜像后,长输入预填充比 0909 版(vllm/vllm-openai:deepseekv41-flash-0909)快得多,TTFT 和 E2E 下降约 60%,测试中没有 OOM 和重启,每卡显存峰值约 93.3 GiB。

长输入的首 token 与预填充

并发 1,输出 16 token,每档 10 个请求,输入长度与目标精确一致

TTFT P50(秒),越低越好

  • 1017 版
  • 0909 版

预填充吞吐(千 tok/s),越高越好

  • 1017 版
  • 0909 版

这轮长上下文测试只完成了一部分:网关切回后出现了业务流量,我中止了测试,所以 240K 并发 4、262K 以上的边界和长短混合都没测。262K、393K 边界和 1 小时 C16 soak 是在 0909 版上通过的,1017 版没有重做。

还有两条运维上的结论。第一,524288 是提示词、聊天模板和输出的总预算:0909 版上输入 524272、输出 16 的请求返回了 HTTP 400,日志没保留错误内容,原因我没确认,所以业务请求要留余量。第二,长请求要隔离。一次混合测试里,一个 240K 请求叠加 300 个并发 16 的短请求,短请求的 TTFT 中位数只有 219 毫秒,P95 却到了 62.4 秒,代价全在尾部;17.4K 输入在并发 100 时 TTFT P99 约 380 秒。长请求应当单独路由并限并发,不能和短请求共用一个高并发池。

引擎选择:vLLM 对 SGLang

我用 SGLang 测了三个镜像,参数照它官方文档里 Hopper 的 TP8·EP8 推荐写法,再对齐 512K 和 Engram 主机内存。下图取最好的一个,lmsysorg/sglang:nightly-dev-cu13-20261014-684fea8a,对 vLLM v0.32.0。

vLLM 对 SGLang 的输出吞吐

tok/s,单轮、每档 100 请求。短 = 256/128,1K = 1K/1K,17K = 17.4K/1K

  • vLLM v0.32.0
  • SGLang nightly-dev-cu13-20261014

单并发时差距不大,并发一上去,SGLang 只有 vLLM 的约一半。另一个镜像 nightly-dev-cu13-20260922-582389ce 更低:1K/1K 并发 100 只有 180.8 tok/s,而且按默认显存比例起服务时,长上下文测试中 CUDA 分配器连 20 MiB 都映射不出来,OOM 了,调低显存比例才跑完。最早的 lmsysorg/sglang:dev-dsv41 在 Engram 表的布局上直接启动失败,换了布局才起来,我在它的评估中途停了下来。启动耗时也有差别:SGLang 首次启动要 16 到 30 分钟,vLLM 约 8 到 10 分钟。

要交代的是,SGLang 这边我按推荐写法跑,没有像对 vLLM 那样逐项调;差距大到调参很难补上,我才中止。这个结论只针对 V4.1 在这台机器上的表现,不是对两个引擎的一般性判断。

调参的结论

改动数据结论
开专家并行(EP8)每卡权重约 49.7 降到 38.1 GiB,KV 从约 776 万升到 1113 万 token;同时把并发序列上限从 16 提到 128,并发 32 的吞吐从 514 升到 737 tok/s采用。两个改动是一起做的,不能把 43% 全算给专家并行
投机解码草稿长度 5 改 3并发 32 三轮均值 735.6 升到 814.5 tok/s(+10.7%);草稿接受率从 24% 左右升到 34% 到 41%采用。草稿 2 的吞吐不低于 3,但 TTFT P95 最多恶化 94%;4 和 5 更差
每步调度的 token 预算翻倍512K 下 KV 容量少 20.9%(17,594,168 到 13,916,531);300 请求乘 3 轮对比,吞吐在噪声内,1K/1K 并发 50 的 E2E P95 从 59.8 秒变成 71.2 秒不调。和并发序列上限一起翻倍,吞吐也只提升 0% 到 2.4%
显存占用比例再提高 3 个百分点KV 容量多 7.6%,吞吐在噪声内;长上下文时每卡峰值已到约 93.3 GiB不调,余量比这点容量值钱
换 MoE 内核并发 1 到 32 为 80、247、473、485 tok/s,默认实现为 87、273、496、514不换。换前端实现等几个变体,也都没在整机吞吐上胜出

前缀缓存是开着的,但压测用的都是互不重复的随机输入,线上收益我没有测。新配置第一次启动要做 JIT 编译和 CUDA Graph 捕获,实测 13 到 29 分钟,之后同配置重启约 7.5 到 10 分钟,所以引擎的就绪等待时间要放宽,别让首次启动被超时判成失败。

压测方法

所有测试,不管打的是 vLLM 还是 SGLang,客户端都是同一个镜像里的 SGLang bench_serving,数据集用本地生成的随机 token,长度固定,最大输出和最小输出设成同一个值,关闭思考。覆盖的场景有:256 输入 128 输出、1K 对 1K、8K 对 8K、17.4K 输入 1K 输出,并发 1 到 100,外加 64K 到 240K 的长输入。几条规则是踩过坑之后加上的:

  • 固定长度预检:开跑前先发一个请求,确认输入和输出的 token 数正好等于目标。SGLang 里把长度范围比例设成 0,表示长度在 1 到目标值之间随机,不能用来做固定长度对比。
  • 每档校验成功数等于请求数、每个请求的输出长度等于目标、错误列表为空,任何一项不满足,这一档标为无效,不进结论。
  • 吞吐按实际生成的 token 数算。冷缓存没有可移植的清除接口,我把“换种子、无前缀复用”定义为冷缓存。
  • 首轮不计入。一次复测里,首轮并发 16 的 TTFT P95 是 5.13 秒,之后三轮是 0.40 到 0.48 秒,首轮的尾延迟不代表稳态。
  • 先检查压测脚本本身。一次短请求矩阵,种子超过了 NumPy 接受的 32 位范围,客户端在发请求之前就抛了错,整个矩阵没有有效数据;同一次测试里 soak 脚本复用了过期的时间预算,循环计数被空转撑大,不能当成功次数。这两处我都只保留了有效的部分。

单机 8 卡,A/B 只能串行,所以分两层筛选:第一层是单轮、每档 100 请求的快速筛选,11 个配置前后跑了十几个小时,门槛是 100% 成功且 TTFT P95 相对基线劣化不超过 15%;入围的 1 到 2 个再做 300 请求乘 3 轮的正式对比,每轮种子不同,两个配置用同样的种子。原生产的基线是网关把它切走之后,在同一口径下补测的单轮数据,这是整套评估里最弱的一环。

结果

正式口径下,1017 版在 5 个场景都比原生产快,场景越偏高并发和长输入,提升越大。

1017 版对原生产的输出吞吐

tok/s。1017 版每轮 300 请求、3 轮均值;原生产是单轮

  • 1017 版
  • 原生产 0909 版
场景提升TTFT P50 / P95TPOT P50
256/128 · C16+5%0.17 / 1.14 s19.3 ms
256/128 · C32+10%0.26 / 0.68 s29.6 ms
1K/1K · C50+17%0.37 / 3.05 s30.1 ms
1K/1K · C100+26%0.63 / 8.02 s47.4 ms
17.4K/1K · C10+80%1.95 / 7.22 s21.1 ms

提升有两条要打折。一是原生产只有单轮基线:筛选阶段同口径的单轮提升是并发 16 的 +13%、并发 32 的 +24%,正式口径只剩 +5% 和 +10%,差别来自 100 请求和 300 请求的口径不同。短请求我更相信正式数字,但基线本身没在 300 乘 3 的口径下重测,这两档的提升可能被低估。二是提升并不全是权重带来的:我把权重和镜像拆开测过,只换权重或只换镜像,并发 50 和 100 都没有稳定收益(0909 权重加 v0.32.0 的并发 50 反而低了 6%),两个一起换才拉开 20% 以上。短请求和长输入的提升主要来自新镜像,高并发则需要两者配合,更细的归因我没做。尾延迟同时下降:并发 32 的 TTFT P95 从 1.29 秒降到 0.83 秒,17.4K 并发 10 从 17.6 秒降到 7.2 秒。

TP4 方案也值得一提:单实例 TP4 在 1K/1K 并发 100 的吞吐其实比 TP8 还高(1840 对 1667 tok/s),但 TTFT P95 比基线差了 35.9%,KV 容量小一个数量级;两个 TP4 实例同时开起来,聚合吞吐在总并发 32 以上都不超过 TP8。TP8 是更合适的单实例形态。

再看 H20 本身。同一个模型分别部署在 H20 和 H800 上,用同一套压测场景对比(H20 一侧是 0909 版,每档 100 请求),H20 的单并发还不错,并发越高差得越多,输出吞吐占 H800 的比例如图:

H20 占 H800 的输出吞吐比例

同一模型、同一套压测场景,每档 100 请求,按并发对比

  • 1K / 1K
  • 8K / 8K
  • 17.4K / 1K

17.4K 场景从并发 10 到 100,输出吞吐几乎不再增长(215 到 245 tok/s)。100 个 17.4K 请求合计约 174 万输入 token,按整机约 4,400 tok/s 的总吞吐要排 6 分半钟,和实测约 380 秒的 P99 TTFT 对得上。我没做 profiling,“高并发时算力成为瓶颈”只是我对这条曲线的解读。

上线与回滚

线上是多个实例并行对外服务,所以升级不需要停服:晚上把这台机器的流量切到其他实例,腾空之后再动它,验证通过再切回来。一次模型升级一般控制在三个晚上的测试窗口内,白天只看数据、改方案,不碰线上。这次 1017 版也是这样:10 月 18 日晚开始评估,10 月 20 日晚完成切换。

上线脚本默认只演示不执行,动手前核对前置条件,包括服务上没有在途请求。顺序是:保存测试容器的完整配置快照,停掉测试容器(只停不删),确认端口释放,再起生产容器并存一份快照。10 月 20 日切换后的验收全部通过:

  • 启动到健康检查通过 9 分 28 秒,重启计数 0。
  • 思考开、关和工具调用都正常;固定长度请求 4 个,输入 261、输出 128 token,全部成功。
  • KV 容量 17,594,168 token,空闲时运行中和排队中的请求都是 0,每卡已用 87,061 MiB、空闲 10,296 MiB,日志里没有 ERROR 和 OOM。
  • 日志里有三条警告,都没拦住服务。其中一条是首批请求会遇到 Triton kernel 的 JIT 延迟尖峰,所以我建议切流之前先发几个代表性请求预热。

回滚靠旧容器:0909 版容器在切换后保持停止状态,没有删除,启动参数另存了一份脚本。回滚是先停新容器、再启旧容器,两者都要占满 8 张卡,顺序不能反。按同镜像候选的启动记录,到健康大约 8 到 10 分钟;旧容器本身的回滚耗时没有实测,也没有演练记录。

如果再部署一次

  1. 先算 KV 预算再定上下文。同样的上限,Engram 表的位置能让 KV 容量差 2.4 倍,调度预算翻倍又吃掉 20.9%;要和“满长请求能并发几路”一起看。
  2. 先固化压测口径,再比较配置。固定长度预检、有效性校验、种子范围检查,每一项都是被一次作废的数据逼出来的。
  3. 筛选用单轮,结论用多轮。同一个配置,100 请求单轮显示并发 32 提升 24%,300 请求三轮只剩 10%;换权重和换镜像要分开测,也要一起测。
  4. 保留旧容器,只停不删,在上线前就演练一次回滚。单机独占 8 卡,评估和回滚都要停掉线上,这是预案里最便宜的保险。
  5. 在 H20 上,高并发和长输入不要指望线性扩展。长请求单独路由并限并发,比调任何一个启动参数都管用;还没补的是 1017 版 262K 以上的边界和 soak,以及线上真实请求长度的分布,后者直接决定 512K 这个上限合不合适。