超过 60 万 tokens/s:DeepSeek 4.1 Flash 在 8×B300 上,vLLM 对比 DeepInfer
DeepSeek 开源了 DeepInfer,也就是支撑其自家服务的推理引擎。我们在一台 8×B300 节点上,用 DeepSeek-V4.1-Flash-1017 把它和 vLLM 正面对比:模型如何放进显存,两个引擎在输出和预填充上分别能跑多快,以及为什么 DeepInfer 在单节点上落后。
01 · 为什么新的推理引擎:DeepInfer
10 月,DeepSeek 开源了 DeepInfer,按官方的说法,它是“支撑 DeepSeek 大规模生产服务的高性能推理框架”。它自带 DeepSeek 也单独发布的内核(FlashMLA、DeepGEMM、DeepEP、DeepSelect),支持数据并行注意力加专家并行 MoE,以及 DSpark 投机解码。DeepInfer 最初基于 vLLM 构建,而 vLLM 也有一份针对该模型在 B300 上的 recipe,用的内核大多相同。
于是问题很直接:在一台 8×B300 上,也就是大多数团队能租到的最大单元,两者差距有多大?我们在两个引擎上运行 DeepSeek-V4.1-Flash-1017,把输出和预填充都推到节点极限。
02 · 两个引擎vLLM 与 DeepInfer:区别在哪
两个引擎运行同一个模型,内核也大多相同,扩展方式也一样:每张 GPU 复制一份注意力,专家分布在各 GPU 上。区别在于定位和形态。vLLM 是装上就能对外服务的通用引擎;DeepInfer 是 DeepSeek 自家服务的核心,但开源时没有带上周边的服务系统。
| vLLM | DeepInfer | |
|---|---|---|
| 定位 | 通用开源推理引擎和服务端 | DeepSeek 的生产引擎,为单一模型开源 |
| 模型 | 数百个开源模型,包括本模型 | 仅 DeepSeek-V4.1-Flash |
| 硬件 | NVIDIA、AMD、TPU 等 | NVIDIA SM100(B200、B300、GB200)和华为昇腾 950 |
| 接口 | 兼容 OpenAI 的 HTTP 服务,支持工具调用和推理解析 | Python API,离线批处理(LLM.generate),没有服务端 |
| 安装 | Docker 镜像或 pip wheel | 从源码构建:GCC 13、CUDA 13,NVCC 13.1 或更新 |
| 亮点 | 前缀缓存、结构化输出、LoRA、通过 KV connector 的 PD 分离 | DSpark 与置信度调度验证、批次不变推理、确定性采样 |
| 未包含 | — | KV 缓存系统、预填充/解码分离、负载均衡 |
| 设计目标 | 任意规模,从单卡开始 | 一个 NVLink 域内:预填充每组 4 卡,解码每组 64 卡 |
03 · 配置8×B300,一个模型,两个引擎
| 系统配置 | |
|---|---|
| GPU | 8× NVIDIA B300 SXM6 AC,每卡 275 GB HBM3e,NVLink 5 经 NVSwitch 互联 |
| 主机 | 192 vCPU、2.7 TB 内存、6 块本地 NVMe 组成 RAID 0(21 TB) |
| 模型 | deepseek-ai/DeepSeek-V4.1-Flash-1017,476 GB:专家为 FP4,其余为 FP8,两张 3.84 亿行的 Engram 表 |
| vLLM | 0.31.1rc1,vLLM B300 recipe 的镜像,NVIDIA 驱动 610.57.04;DEP8,每个 rank 256 条序列、每步 8K token,DSpark 5 个草稿 token |
| DeepInfer | deepseek-ai/DeepInfer 8b16149(10 月 20 日),用 CUDA 13.2 从源码构建;EP8 × DP8,DSpark 5 个草稿 token,离线 API;B300 SXM6 PC,驱动 580 |
| warmctl | v2.0.0-beta.10,快照 deepseek-v41-flash-1017:8xb300ac-275gb-610.57.04,851 GiB |
| 负载 | 通过 OpenAI API 运行 vllm bench serve:随机 token 提示,固定输出长度(--ignore-eos),提示数为并发数的两倍;DeepInfer 通过 LLM.generate 在进程内接收同样的提示 |
vLLM 的数据来自 warmctl 恢复的引擎,并与前一天在另一台节点(B300 SXM6 PC)上原生启动的同一 recipe 对比。随机提示是投机解码的最坏情况:DSpark 在 1K 时接受了 19% 的草稿,8K 时 12%。真实文本的接受率更高,因此这里的解码数据都是下限。
04 · 布局DEP8 如何把模型放到八张 GPU 上
这是一个混合专家模型:每层 384 个路由专家加 1 个共享专家,每个 token 激活 6 个路由专家。注意力是 MLA 的稀疏变体:128 token 的滑动窗口,加上一个从更早的 token 中挑出最相关 512 个的索引器,因此每步开销几乎不随上下文增长。两张 Engram 表是模型查表而非计算得到的 n-gram 嵌入,占检查点的 40%。
vLLM 的 recipe 以 DEP8 运行:数据并行注意力、专家并行 MoE。每张 GPU 运行自己的一份注意力,处理自己的请求和 KV 缓存,并持有 384 个专家中的 48 个。在每个 MoE 层,GPU 之间通过 NVSwitch 交换 token,让每个 token 到达它被路由到的 6 个专家,再把结果交换回来。
每张 GPU 还剩很多空间。一个 rank 需要的权重是 46 GiB;profile 固定预留 160 GiB 给 KV 缓存,使用 DeepSeek 的 NVFP4 MLA 格式,vLLM 统计每个 rank 能容纳 52 百万个 token,相当于 264 个满 196K 上下文的请求。限制这台节点的不是显存,而是每个 rank 256 条运行中的序列,整节点 2,048 条。
05 · vLLM 满负载输入 65.8 万 tokens/s,输出 6.5 万
先说预填充,因为大数字在这里。我们发送只输出一个 token 的提示,节点只做读取。2,048 个并发 8K 提示下,vLLM recipe 每秒处理 658,259 个 token,每张 GPU 约 8.2 万;512 个并发 32K 提示时为 589,924。提示越长,每个 token 越便宜:同样并发的 1K 提示是 462,366。
解码方面,2,048 个并发请求在短提示时得到 每秒 64,990 个输出 token,1K → 1K 时为 54,985,每张 GPU 约 6,900,每个 token 29 毫秒。8K 提示时输出降到 29,500,但节点同时还在每秒读取 235,996 个提示 token。2,048 也是上限:每个 rank 256 条序列。4,096 个请求时,多出的请求在队列中等待,输出保持在 55,506。
| 输入 → 输出 | 并发 | 输出 tok/s | 总 tok/s | 原生,输出 | TPOT |
|---|---|---|---|---|---|
| 128 → 1K | 2,048 | 64,990 | 73,114 | — | 24.8 ms |
| 1K → 1K | 2,048 | 54,985 | 109,969 | 53,892 | 28.8 ms |
| 1K → 1K | 4,096 | — | — | 55,506 | 32.8 ms |
| 8K → 1K | 2,048 | 29,500 | 265,496 | 29,580 | 52.8 ms |
| 2K → 128 | 2,048 | 19,753 | 335,801 | — | 74.4 ms |
解码数据来自 warmctl 恢复的引擎;预填充峰值来自前一天在 B300 SXM6 PC 上原生启动的 recipe。在两边都测过的点上,差异在 2% 以内:1K → 1K 为 54,985 对 53,892,8K → 1K 为 29,500 对 29,580。恢复会把引擎原样带回,包括 CUDA graph 和自动调优过的内核。在一半并发下,恢复的引擎预填充分别为 554K 和 557K。
06 · DEEPINFER为机柜而生,却跑在单节点上
让 DeepInfer 跑起来比测它花的时间更长。它需要从源码构建,要求 NVCC 13.1 或更新,我们用了 NVIDIA 的 CUDA 13.2 wheel。它通过 NCCL 的 GPU 发起通信在 GPU 之间搬运 Engram 行,即使在单节点上也需要 RDMA 设备:在一台没有可用 RDMA 的 8×B200 虚拟机上它根本启动不了(DeepEP#789),在这台 B300 上,驱动 580 可以运行,610 不行。
开源版本只支持离线:没有 HTTP 服务,请求通过 LLM.generate 提交。我们在进程内给它同样的随机 token 负载,这让它省掉了 vLLM 数据里包含的 API 服务、分词和网络开销。我们运行了两次:一次用默认设置,一次按 DeepSeek 的 README 和示例脚本为 V4.1 调优。
| DeepInfer 设置 | 调优运行(10 月 21 日) | 原因 |
|---|---|---|
| 并行 | EP8 × DP8:8 个引擎,专家分布在全部 8 张 GPU 上 | 与 DeepSeek 一致 |
| Bounded replay | YOCO_PREFILL_TRUNCATION_MODE=compact,140 个 token | README:V4.1 的默认设置 |
| DSpark | 5 个草稿 token,按置信度调度验证(ENABLE_MTP_VARLEN=1) | README |
| 专家 | 32 个冗余专家,每 30 步做一次 EPLB | DeepSeek 的示例脚本 |
| Engram | 表放在 GPU 显存中(ENGRAM_FORCE_GPU_STORAGE=1) | B300 显存充足,省去 190 GiB 锁页主机内存 |
| 批处理 | 每个 rank 256 条序列、每步预填充 32K token | 与 vLLM 限制相同;DeepSeek 基准的默认步长 |
| 主机 | 驱动 580、CUDA 13.2、锁页内存不限、RDMA 网卡 | DeepInfer 在驱动 610 上无法启动 |
纸面上最重要的是 bounded replay。V4.1 的第 21 到 39 层复用前面四层的 KV 缓存,并通过 128 token 的窗口做注意力,所以提示中较早的 token 不需要经过这些层;开启后,DeepInfer 只让每个提示的最后 140 个 token 经过它们。
解码上 vLLM 全面领先:短提示 1.8 倍,1K → 1K 2.0 倍,8K → 1K 4.0 倍(每个请求要先预填充 8K token)。DeepInfer 在 1K → 1K 的最好成绩 27,906 来自默认运行;调优运行为 21,161,很可能是因为 EPLB 每 30 步移动一次专家,而随机 token 本来就均匀分布在各专家上,这种移动得不到回报。
预填充差距更大。调优后,DeepInfer 处理 8K 提示 71,632 tokens/s,32K 提示 71,875,每张 GPU 约 9K;vLLM 分别为 658,259 和 589,924。Bounded replay 让 DeepInfer 的 32K 成绩从 64,847 提高了 11%,而不是跳过的层所暗示的两倍,所以时间花在了别处。
| 负载 | 并发 | vLLM | DeepInfer 调优 | DeepInfer 默认 | vLLM ÷ 最佳 |
|---|---|---|---|---|---|
| 128 → 1K | 2,048 | 64,990 | 36,381 | — | 1.8× |
| 1K → 1K | 2,048 | 54,985 | 21,161 | 27,906 | 2.0× |
| 1K → 1K | 4,096 | 55,506 | — | 26,294 | 2.1× |
| 8K → 1K | 2,048 | 29,500 | 7,297 | 6,933 | 4.0× |
| 预填充 8K | 2,048 | 658,259 | 71,632 | — | 9.2× |
| 预填充 32K | 512 | 589,924 | 71,875 | 64,847 | 8.2× |
DeepSeek 自己的预填充曲线是每张 GPU 8.2–8.9 万 tokens/s,约为我们测到的九倍。它来自不同的配置:每组预填充 4 张 GPU,强制专家负载均衡(其基准脚本中的 DEEPINFER_FORCE_LOAD_BALANCE=1),以及在已缓存上下文之上预填充 4K token 的后缀。我们的节点也缺少 MLOPart——DeepGEMM 用它把 MoE 缓冲区放在 B300 正确的那一半芯片上,而驱动 580 不提供它。我们无法在单节点上复现 DeepSeek 的数字,也没有对 DeepInfer 的预填充做性能剖析。
README 说明了 DeepInfer 的设计目标:解码用 64 张 GPU、预填充用 4 张,“各自位于一个 scale-up 域内”,预填充和解码在不同机器上。把这种部署串起来的部分——KV 缓存系统、预填充/解码分离和负载均衡——不在开源版本里。在我们的节点上,DeepInfer 必须在同样的八个引擎里同时做所有事,而它的调度器每一步要么全做预填充、要么全做解码,从不混合。这正是 vLLM recipe 针对的场景,而不是 DeepInfer 的设计场景。
07 · 启动15.5 分钟,还是 88 秒
引擎日志显示了 930 秒冷启动的去向,这是在全新磁盘、缓存为空的情况下。加载权重只占一小部分:96 秒,包括 Engram 表。其余都在让 GPU 就绪:84 秒 torch.compile 和 KV 初始化,110 秒 JIT 内核预热,319 秒 FlashInfer 自动调优(21 个 token 档位),以及 199 秒捕获 67 个 CUDA graph。前一天在另一台节点上原生启动的 vLLM recipe 用了 24 分钟才就绪。DeepInfer 在 JIT 缓存已热时加载用了 298 秒,缓存为空时 748 秒。
warmctl 把慢的部分只做一次:构建引擎、预热,然后整体做快照:全部 27 个进程的内存(CRIU)、每张 GPU 的 CUDA 状态(cuda-checkpoint)、编译好的内核、自动调优结果和 CUDA graph,以及引擎布局好的 372 GiB 权重,总共 851 GiB。warmctl up 从页缓存 88.4 秒恢复:43 秒恢复进程,22 秒恢复 GPU 状态,12 秒重新加载权重,然后用一个 canary 提示与原引擎比对,再开放端口。节点以 155 GiB/s 从页缓存读取快照。恢复后我们用贪心解码检查的每个回答都正确。
快照已在 warmctl 目录中。在装有 NVIDIA 驱动 610.57.04 的 8×B300 主机上:
- 8×B300·88 s·851 GiB·610
deepseek-v41-flash-1017Run$ curl -fsSL https://warmctl.io/install.sh | sh $ warmctl up deepseek-v41-flash-1017
up 会根据主机的 GPU 和驱动选择快照,并在 8000 端口提供兼容 OpenAI 的 API:
$ curl -s localhost:8000/v1/completions -H 'Content-Type: application/json' -d '{ "model": "deepseek-v41-flash-1017", "prompt": "The capital of France is", "max_tokens": 8, "temperature": 0}' | jq -r '.choices[0].text' Paris.
profile 中包含 vLLM 参数、graph 尺寸和预热网格。同一模型在目录中还有 8×B200(DEP8,驱动 615)、4×B200(TP4)和 8×B300 驱动 580(TP8)的版本。
08 · 如何选择什么时候用 vLLM,什么时候用 DeepInfer
基准测试回答的是单节点上谁更快。选哪一个,取决于你要做什么。
- 在一台或几台节点上为用户提供 API:vLLM。在这台节点上它的输出是 DeepInfer 的 2–4 倍、预填充是 8 倍,而且自带兼容 OpenAI 的服务端。DeepInfer 的开源版本根本没有服务端。
- DeepSeek-V4.1-Flash 以外的任何模型:vLLM。DeepInfer 只支持这一个模型。
- 需要可复现的离线生成,比如 RL rollout、评测或合成数据:可以试试 DeepInfer。批次不变推理和确定性采样是内置的,并且针对本模型有文档,而离线批处理 API 本来就是这类任务的用法。先在你自己的负载形状上测吞吐;在我们的测试中解码只有 vLLM 的一半到四分之一。
- 在机柜级 NVLink(GB200 或 GB300 NVL72)上搭建 DeepSeek 服务,并有团队运维:两者都值得看。DeepInfer 就是 DeepSeek 在这种规模上服务该模型的方式,但 KV 缓存系统、PD 分离和负载均衡要自己搭。vLLM 通过 KV connector 的分离式预填充覆盖了这部分。
- 华为昇腾 950:DeepInfer,它在 NVIDIA SM100 之外也支持昇腾。
- 自动扩缩容、抢占式实例或频繁发布:无论用哪个引擎,冷启动都是关键。这个引擎每台节点要 15 分钟才能出第一个 token,vLLM recipe 在另一台节点上用了 24 分钟。用 vLLM 时,warmctl up 能在 88 秒内把同一个引擎带回来。
对大多数只有一两台节点的团队来说,答案是 vLLM。DeepInfer 值得关注,它展示了 DeepSeek 自己如何运行这个模型,也值得在确定性离线任务上一试。在单节点上、按 DeepSeek 推荐的设置运行时,它不是更快的那个。