vLLM 团队实现 RL 训练与推理逐位一致,DeepSeek-V4-Flash 上 diff 为 0
Finish RL Training Faster: Bitwise-Consistent, High-Performance, Agent-Driven
vLLM 团队提出让训练前向直接调用 vLLM 推理 kernel 并改造这些高性能 kernel,使 rollout 与训练对每个采样 token 的 logprob 逐位相同(diff = 0)。
原文给出训练与推理数值不一致的具体来源和逐算子对齐方案,并附开源代码与技能包,可迁移到其他模型。
TL;DR
- 问题:高性能 RL 中推理侧的优化改变了数值,因此训练优化的策略与采样所用的策略并不一致。在 DeepSeek-V4-Flash 上,高性能基线的训练-推理 KL 从 2e-3 升至 29.9,同时奖励回落。
- 方法:训练前向直接调用 vLLM 的推理内核,并修改这些高性能内核,使 rollout 与训练对每个采样 token 产生逐位相同的 logprob(diff = 0)。
- 结果:在 100 个 DeepSeek-V4-Flash 训练步中,每一步 diff 均为 0,rollout 吞吐量达到基线的 91.3%。迁移到 Nemotron(Mamba 混合 + NVFP4)后,一致运行的 SWE-bench Pro 分数保持稳定,而基线从 0.56 降至 0.22。
- 开源:该流程被封装为 agent skill,并随配方和代码一同发布,降低了在新模型上执行此操作的成本。
- 进行中:DeepSeek-V4.1 已达到 0 diff;性能工作正在收尾,将单独发布文章。

DeepSeek-V4-Flash 率先达到 0 diff;该流程成为 agent skill,随后被应用于 Nemotron-3.5(Mamba-2 混合模型)。左:DeepSeek-V4-Flash 训练-推理 KL;基线从 2e-3 攀升至 29.9,而一致运行每一步均为 0。右:Nemotron-3.5 在 SWE-bench Pro 上的训练分数(5 步滚动均值);一致运行保持在 0.6 左右,而基线降至 0.23。
Rollout 在 RL 训练时间中占主导,因此训练系统采用低精度计算、低精度 KV cache 以及通信和内核优化。这些都会改变推理侧的数值。推理引擎生成响应;训练引擎重新计算其概率并更新模型。在权重相同的情况下,两侧现在对同一 token 赋予不同的概率,因此训练优化的策略与采样所用的策略并不一致。这种差距会随更新而扩大,并使训练不稳定。在我们的 DeepSeek-V4-Flash-Base 实验(未使用 TIS 等算法校正)中,高性能基线的训练-推理 KL 从 2e-3 升至 29.9,同时奖励回落。
训练不稳定意味着更多崩溃、回滚和重跑,有效训练占比更低,达到相同结果所需的总时间更长。我们的目标是更快完成有效的 RL 训练:通过减少崩溃和重跑,以更短的端到端时间达到结果。
我们的方法:在高性能数值路径上,训练和推理对每个采样 token 产生逐位相同的 logprob(diff = 0;第 2 节给出精确定义),这才是真正的 on-policy RL。我们通过让训练前向直接调用 vLLM 的推理内核,并修改这些高性能内核,使结果不随 batch 组成、prefill 或 decode 阶段、或并行布局而变化(第 3 节和第 4 节)。随后,一个 agent skill 降低了在新模型上执行此操作的成本,并使该流程可迁移:它告诉 agent 如何找到两侧出现分歧的第一个算子,将其缩减为最小复现,修复它并重新运行回归测试。在 DeepSeek-V4-Flash 上,我们以高性能基线 rollout 吞吐量的 91.3% 达到 0 diff,并将该方法迁移到 Nemotron——一个采用 NVFP4 的 Mamba 混合模型。
本文衡量数值一致性、训练曲线和 rollout 吞吐量。端到端收益来自更少的崩溃和重跑,这只有在长时间运行中才会显现;该墙钟时间对比留待后续。
1. 现有方法:它们如何权衡这三者
更快完成有效的 RL 训练需要同时具备三个特性:
- A,高性能:快速 rollout 和训练,低精度计算、低精度 KV 缓存和通信优化均可使用;
- B,高一致性:训练和推理对相同权重计算相同的策略;
- C,低成本与通用性:在新模型、精度或硬件上获得 A 和 B 所需的开发时间可接受。
这些特性很少能同时成立。放弃 B,你可以自由优化,但面临训练不稳定的风险。放弃 A,你可以使用一致的 kernel,但推理会慢得多(数字见下表)。同时保持 A 和 B 意味着要逐模型、逐算子对齐,因此 C 无法满足。
训练-推理不匹配并非新问题;现有方法对 A、B、C 的权衡各不相同:
| 方法 | 保留了什么 | 放弃或付出了什么 |
|---|---|---|
| 算法校正:TIS、MIS、FlashREINFORCE、Score Centering | A 和 C:不触碰推理引擎,模型无关,采用成本低 | B:数值差异仍然存在;只是限制了它对更新的影响。代价:偏差、丢弃样本,且随着低精度扩大差距,校正变得更困难 |
| R3(replay rollout expert routing) | A,以及针对 MoE 路由的 B | 仅消除路由翻转;其他数值差异仍然存在 |
| QAT(训练前向模拟部署量化) | A:rollout 可以安全使用低精度;缩小量化引起的差异部分(改善 B) | 训练必须模拟量化,反向传播使用直通估计器(STE:反向传播将量化视为恒等);仅模拟权重会留下来自激活、KV、kernel 和归约的差异 |
| 严格数值对齐(Thinking Machines、slime、IsoExec、本文) | B | A 或 C:通用 batch-invariant kernel 牺牲 A(Thinking Machines 工作负载:26 秒到 55 秒,使用改进的 attention kernel 为 42 秒);在高性能路径上对齐则保留 A 而牺牲 C(slime、IsoExec、本文:逐模型、逐算子的对齐与验证) |
算法校正涵盖多种机制:
- TIS(Truncated Importance Sampling,Yao et al.):按训练/rollout 概率比对梯度加权,在上界处截断,以少量偏差换取有界方差;
- MIS(Masked Importance Sampling,When Speed Kills Stability):不截断;丢弃序列级概率比超过阈值的整个序列;
- FlashREINFORCE(Hu, Zhang et al.):用于异步 agentic RL;轨迹级漂移过滤(序列信任域)移除偏离过远的轨迹;
- Score Centering(Marek & Ryabinin):通过加性校正项抵消每次更新累积的训练-推理漂移;可与重要性采样叠加。
这些方法减轻了不匹配的影响,但并未消除它:不匹配会将同策略训练变为异策略训练(Yao et al.);当不匹配程度较大且来源多样时,token 级重要性采样是有偏的,训练更容易崩溃(Li & Liu);在 P1-VL 中,截断重要性采样只是推迟了崩溃。R3(Rollout Routing Replay,Ma et al.)记录 rollout 的专家路由并在训练中重放,仅消除了路由翻转这一部分。
在最后一行,Thinking Machines Lab 通过批不变内核(Defeating Nondeterminism in LLM Inference)实现了真正的同策略 RL,其推理时开销如表所示,其中大部分来自尚未充分优化的 FlexAttention 集成;vLLM 和 TorchTitan 在训练中复用 vLLM 的前向内核,其位级一致的运行以更少的步数达到了更高的奖励;slime 在 GLM-5 架构(MLA + DSA,与 DeepSeek-V3.2 结构相同)上对齐了 logprobs;而由 Alexander Jiang 和 SkyRL 团队开发的 IsoExec 覆盖了线性注意力(第 6 节)。
这些方法大多可以叠加:一些针对同一权重版本上引擎之间的数值差异,另一些针对权重版本之间的策略差异。在异步训练中,来自陈旧权重的异策略问题仍然交由 TIS、MIS 或 Score Centering 处理,但引擎仍可在这些权重上实现数值对齐,而位级一致的运行则成为衡量此类修正的基线。QAT 也属于严格对齐:只有当训练前向应用相同的量化时,低精度 rollout 与训练才能位级匹配;我们的反向传播使用 STE。
我们采用保留 A 的最后一行路径,A 的标准为:rollout 吞吐量至少达到同等优化的高性能基线的 70%(rollout 是 RL 训练中最耗时的部分;训练侧开销未单独测量,见 3.2)。C 是这条路径的薄弱点;第 5 节介绍 Agent + skill 如何弥补这一点。
2. 训练-推理一致性需要什么
在本文中,diff 指的是在每次更新前,对于同一权重版本和 token 前缀,rollout 对每个有效 token(响应掩码内采样的 token)的 logprob 与训练侧重新计算的 old-logprob 之间的差异。验收要求每个 token 差异 |p_rollout − p_train| 的最大值、均值和标准差,以及 k3 KL,全部恰好为 0。这是精确相等,而非阈值,在每次更新前检查;小批量更新无论如何都会改变策略,这一定义将其排除在外。它在我们已验收的任务(数学推理、多轮工具调用)和并行布局上成立,包括两侧使用不同布局的情况。
“训练和推理计算的是同一件事”可分解为四个维度,外加一个共享的数值契约:
train-inference consistency ⇐ kernel determinism + batch independence + phase consistency + cross-GPU consistency + same numerical contract on both sides
内核确定性(单 GPU)。 同一内核在相同输入上总是给出相同输出。GPU 默认并不保证这一点。一个例子是 split-K 矩阵乘法:不同的累加顺序会给出不同结果,因为浮点加法不满足结合律。
批次独立性。一个请求的结果不得依赖于同批次的其他请求。在推理过程中,引擎会为了吞吐量动态地合并和拆分批次。在训练中,微批次大小、打包以及每个专家的 MoE token 数量也会改变路径。各种优化(按批次大小选择内核、动态拆分数量)使得给定请求依赖于其批次中的其他请求。
阶段一致性。一个 token 在 prefill(一次处理多个 token)和 decode(一次一个)中必须得到相同的结果。推理引擎为每个阶段提供其最快的内核;强制使用共享路径是一致版本速度下降的主要来源,也是优化的重点。
跨 GPU 一致性。跨 GPU 归约(AllReduce、reduce-scatter,以及 MoE all-to-all 后合并专家输出)也必须在两个层面上固定下来。
- 在两侧具有相同并行布局的情况下,通信结果不得随时间和调度而变化(固定通信库的算法、归约分块和精度,以及专家合并顺序)。
- 在不同并行布局(例如不同的 TP/EP 度)的情况下,结果也必须随着并行度的变化而保持不变,这需要固定的归约树并且成本更高。DeepSeek-V4 的方案则是将跨 GPU 归约移出被比较的路径(4.5)。
每个维度都防范两个根本原因之一:不同的操作顺序(并行累加或归约顺序跟随调度)或不同的计算路径(两侧,或不同的批次形状,运行不同的内核或分支)。达到 0 diff 意味着首先消除静态差异,然后固定动态路径选择使其忽略负载。
除了这四个维度之外,达到 diff = 0 还需要一些其他条件:
- 两侧相同的数值约定:相同的量化和舍入、相同的累加精度、相同的内核算术。两个引擎可以满足所有四个维度,但如果它们的计算方式不同,仍然会不一致。
- 相同的离散选择:Top-K 和 MoE 路由会将一个 ulp 的上游差异(浮点数最后一位的一个单位)转化为不同的 token 集合或专家组合。一旦上游匹配,平局打破、输出顺序和合并规则也必须匹配。
- 每步 1 次更新(步批次等于小批次):如果每步有多次小批次更新,从第二次开始,训练权重与采样权重不同,这是 diff = 0 不覆盖的,因此训练不再是严格意义上的 on-policy(在一个每步 4 次更新的数学任务中,每次更新后约有 7% 的 token 切换了专家组合)。
- 正确的反向传播和恢复:这四个维度只给出了一致的前向。反向传播仍然必须正确,并且检查点恢复后的第一步也必须保持一致。
第 4 节中的主要算子各自满足这些要求之一:dense GEMM 固定内核选择以实现批次独立性(4.1);FP8 KV 缓存统一 prefill 和 decode 读取以实现阶段一致性(4.2);Top-K 固定平局和顺序以使离散选择匹配(4.3);专家 GEMM 在两种布局之间保持一种数值约定(4.4);跨 GPU 归约移出被比较的路径以实现跨 GPU 一致性(4.5)。
diff = 0 表示两侧计算的是同一件事,而不是它适合训练:当每步有多次更新时,diff = 0 成立,但路由会在更新之间漂移。diff = 0 是我们的工程目标,而不是训练质量的充分条件,训练质量需要单独验收。
3. 参考配方:DeepSeek-V4-Flash
DeepSeek-V4-Flash 结合了低精度计算、稀疏注意力和多节点专家并行,使其成为一种具有挑战性的配置。在 FP8 基础上,我们在 GB200 上训练了两个 100 步的运行:一个优化的高性能基线和一个 diff = 0 的一致性版本。
任务:使用 DAPO 格式数据、GRPO 以及带有超长惩罚的奖励(因此奖励可能为负)进行数学推理。每次运行使用 8 台机器(32 个 GPU),每步 128 个提示 × 8 个响应,每步 1 次更新(步批次 = 小批次)。两次运行均不使用 TIS、MIS 或类似校正,从而直接暴露引擎差异。
并行策略:DP 数据、EP 专家、CP 上下文(序列拆分)、TP 张量。一致性版本的 rollout 运行 4 个 vLLM 引擎,每个 DP8/EP8,TP=1;训练使用 EP32、CP4、TP=1。两侧使用不同的并行布局(4.5)。
相关结构:MLA(多头潜在注意力,在缓存前压缩 KV);DSA(DeepSeek 稀疏注意力:轻量级索引器对历史位置打分,Top-K 最多保留 2048 个,MLA 仅关注这些位置);CSA 压缩器(将历史 KV 压缩为更少的行);mHC(跨残差流混合,混合矩阵通过 Sinkhorn 迭代归一化);以及 43 个 MoE 层。SM100 是 Blackwell(此处使用的 GB200);SM90 是 Hopper。
3.1 核心决策:推理内核定义数值,训练添加反向
两个引擎最容易通过运行相同代码达成一致;问题在于哪一侧做出调整。Rollout 主导 RL 时间,每个算子都针对吞吐量进行了调优;让训练成为参考将放弃这些优化。因此,推理内核是数值参考:训练前向调用相同的 vLLM 内核来获取其数值,训练只添加反向。推理也不会切换到通用的批次不变内核:它保留高性能部署内核并修改它们以满足第 2 节的要求。第 4 节中的每个算子都遵循这一权衡。
这一决策的挑战在于推理内核没有反向,且 FP8 量化不可微。因此前向和反向分开处理,前向数值始终来自 vLLM 内核。每个模块将内核包装在自定义的 autograd.Function 中,其反向针对 BF16 主权重进行微分,FP8 量化被视为恒等(直通估计器,STE)。反向主要复用现有内核:线性层和专家 GEMM 使用 Transformer Engine BF16 GEMM,稀疏注意力使用 cuDNN 前端的 DSA 反向,all-to-all 使用 DeepEP 的反向;只有 mHC、RMSNorm 和 SwiGLU 等小型算子有手写的 FP32 导数。少数融合内核的输出无法通过这种方式微分(注意力 q/k、压缩 KV、路由权重、最终 logprob);对于这些,我们仅在 PyTorch 中重新运行相同计算以获取其梯度,并保留内核的数值:visible + (graph - graph.detach())。每次反向首先检查主权重自前向以来未发生变化,因此重计算或异步更新不会混入权重版本。
所有成本都落在训练上,分为两类。工程与验证:梯度是一个替代量,而不是量化算子的精确导数,因此逐位相同的前向并不能证明梯度正确,该技能会对照独立参考来检查反向(第 5 节);反向只需正确,无需确定性。每个布局入口点(例如 4.4 中的 masked / contiguous)也需要各自的验证。性能与内存:训练前向必须保存推理内核的输入和输出以供反向使用;内存压力正是该方案启用完全重计算的原因,而完全重计算会运行两次前向。
3.2 结果与谁买单

每步训练奖励。橙色:基线;蓝色:一致版本。粗线:当前及前 4 步的移动平均;细线:原始值。

上:每步最大训练-推理概率差异;下:训练-推理 KL(k3 估计量 r − 1 − log r,全文使用;对称对数刻度,零附近为线性)。最大差异会对一次路由翻转或 Top-K 交换做出反应,因此从基线第 1 步起就相当大;KL 显示整体水平。橙色:基线;蓝色:一致版本。一致版本的两项指标在每一步均为 0。
在 100 步中,一致版本的 rollout 平均为 24.22 token/ms,是基线(26.53)的 91.3%:

每步 rollout 吞吐量(输出 token/ms,在 32 块 GPU 上求和)。橙色:基线;蓝色:一致版本。粗线:当前及前 4 步的移动平均;细线:原始值。
基线的训练-推理 KL 增长(2e-3 到 29.9),同时其奖励回落;一致版本保持 diff 为 0,其奖励也保持稳定。另一个潜在收益是少一次前向:我们每一步重算 old-logprob 只是为了测量 diff;验收后,训练可以使用 rollout logprob 代替。
Rollout 侧,在本次运行中测量:平均吞吐量为基线的 91.3%(约低 9%),高于第 1 节的 70% 下限。第 90 步后基线的吞吐量下降,这抬高了比值;在相同步数内,其平均响应长度从约 3,000 降至约 1,600 token。前 89 步给出 88.6%。两次运行的响应长度在整个训练过程中都不同,因此该比值也反映了不同的工作负载,而不仅仅是引擎。
这一差距尚未归因于算子;第 4 节指向 MoE LL 通信与后端、注意力 gather 与反量化,以及每请求启动。
训练侧,未单独测量:根据第 4 节,开销在于每次前向都要重新量化 FP8 权重、串行的每请求注意力和 Top-K、一次额外的路由元数据分发,以及完全重计算(3.1),后者会运行两次前向。
一份单独的 verl recipe PR 预览在 32 块 GB200 GPU 上报告了约 10% 的端到端开销:rollout 接近持平,差距主要在训练。训练侧优化是下一步。
4. 逐算子:DeepSeek-V4 recipe 中的技术选择
每个小节涵盖保留了什么、放弃了什么、如何恢复速度,以及还剩下什么成本。每一侧每个算子使用哪个内核,以及反向如何编写,都在 skill 中。
逐算子主表
成本主要在于 MoE 通信与后端(4.4、4.5),其次是注意力 gather 与反量化(4.2、4.3);dense 很便宜。
| 模块 | 保留 | 放弃 | 成本 |
|---|---|---|---|
| Dense GEMM(4.1) | 部署 W8A8 与 DeepGEMM;每个输出一种累加顺序 | M<32 时的 FlashInfer 分支、小批量门控内核、融合 mHC 内核 | rollout:没有更快的小批量内核;训练:每次前向都重新量化权重 |
| FP8 KV 缓存(4.2) | FP8 存储 | 直接读取 FP8 的解码内核 | rollout:解码时额外的 gather 和反量化;训练:一份私有缓存 |
| Top-K(4.3) | DSA 稀疏注意力 | 默认 Top-K 平局打破与输出顺序 | 每侧多一次排序,通过融合弥补;每个请求多次 prefill 启动;训练:每个请求的注意力和 Top-K 串行 |
| 专家 GEMM(4.4) | FP8 分组 GEMM;路由按构造匹配,无 R3 | FlashInfer TRT-LLM MoE | rollout:主要吞吐差距;训练:对齐填充 |
| 跨 GPU 归约(4.5) | 训练和 rollout 使用不同的并行布局 | TP>1 | rollout:LL 通信无法与计算重叠;训练:多一次路由元数据分发 |
| CUDA Graph(4.6) | 图 | 仅性能的捷径分支 | 工作区按其上限预留 |
| 其他(embedding、RMSNorm、o_proj、lm_head + logprob) | 现有内核,固定归约顺序 | — | 小 |
4.1 Dense GEMM:保留 FP8,每个 M 使用一种累加顺序
保留。DeepSeek-V4-Flash-Base 以 block-FP8 形式发布,部署路径为 W8A8 加 DeepGEMM。一致版本在两侧都使用它:每次训练前向按部署规则将 BF16 主权重量化为 block FP8,并调用推理 GEMM。BF16 rollout 会消耗计算和带宽;而 MXFP8 等独立的训练侧方案只能接近部署的数值,即第 1 节 QAT 行的局限。
放弃。推理引擎按形状选择内核:FP8 线性层在 M<32 时使用 FlashInfer,否则使用 DeepGEMM;路由门控在 16 个 token 或更少时切换到小批量内核;mHC 分割数随 token 数增长。浮点加法不满足结合律,因此每种选择都会使请求的结果依赖于其邻居。一致版本移除了这些分支:FP8 线性层始终以批量不变模式使用 DeepGEMM,路由门控在所有批量大小下使用一个 FP32 输出 GEMM,小型 BF16 矩阵乘法使用固定求和顺序的 Triton 内核,mHC 分割固定为 1。我们统一采用 DeepGEMM 而非 FlashInfer,因为 DeepGEMM 具有批量不变模式,已是部署内核,且训练 M 较大。

横轴:每步 token 数(示意)。默认路径在 32 处切换内核,因此同一请求在 8-token 和 200-token 步骤中分别经过 FlashInfer 和 DeepGEMM,最后一位不同。批量不变模式只有一种求和顺序。
成本。Rollout 失去了更快的小批量内核和一次 mHC 融合,但 Tensor Core 仍在使用。训练每次前向都重新量化权重,包括重计算;缓存 FP8 权重可节省该时间,代价是多一份常驻副本。
4.2 FP8 KV 缓存:保留 FP8 存储,统一读取路径
保留。KV 缓存以 FP8 存储,约为 BF16 的 57%,直接决定并发量。两次运行共享写入路径。
放弃。默认解码使用 FlashMLA 内核,直接读取 FP8 并按批次划分工作;预填充反量化为 BF16 并运行稀疏注意力。两个阶段运行不同的算法,而解码的划分取决于其批次。一致性版本将解码路由到预填充路径:反量化并将选中的 KV 收集到 BF16 工作区,然后调用相同的 flash_mla_sparse_fwd。工作区按请求分配,大小仅由模型限制决定。必须显式选择此解码内核;仅靠一致性开关不会改变它。

上:之前,预填充和解码用两种算法读取相同的 FP8 历史,解码的划分取决于批次。下:预填充、解码和训练(使用私有缓存副本)共享一条读取路径。
训练没有真正的缓存,但 K 必须完成相同的 FP8 往返:同一个融合内核将其写入私有 FP8 缓存,同一个反量化将其读回。
成本。解码增加了收集和反量化,训练增加了缓存。这是阶段一致性的代价;存储精度不受影响。每个请求当前每步生成一个 token;多 token 预测(MTP)和投机解码不在覆盖范围内。
4.3 稀疏注意力中的 Top-K:修复平局和顺序
问题。DSA 索引器为每个历史位置打分,Top-K 最多选择 2048 个用于注意力。这种离散选择将上游一个 ulp 的差异变成不同的上下文。首先对齐打分:索引器使用相同的 DeepGEMM 内核,预填充读取连续 KV,解码读取分页 KV,两次读取逐位相同。

上:64 位选择键的布局。下:K = 2 的示例,其中位置 17 和 42 平局,键中的位置位选择 42;然后选中的位置按位置降序排序,无效条目排在最后。
修复选择和顺序。默认 Top-K 按线程调度打破平局。一致性版本将分数和请求内位置组合成唯一键,因此平局时选择较大的位置。它使用请求内位置,而非全局批次位置;否则在批次内移动请求会改变平局结果。仅相同的集合还不够:索引顺序决定了注意力的累加顺序,因此选中的索引按位置降序排序,无效条目排在最后。
R3 风格的替代方案记录 rollout 的索引并在训练中重放,但每个 token 每层最多 2048 个索引:对于长响应,每个序列达到 GB 级。
训练侧。训练前向使用相同的打分和 Top-K 选择相同的位置,但索引器不参与训练,主要是为了简单。这与现有实践一致:DeepSeek-V3.2 在训练 DSA 时分离索引器的输入,因此 RL 损失永远不会到达它,而 GLM-5 在 RL 中默认冻结索引器。
成本和恢复。每一侧多做一次排序。确定性 Top-K 每次调用约耗时 5.9 µs;将其与索引排序融合后,一致性版本的推理基准吞吐量提高了 6.7%(配置见 4.4)。剩余成本是预填充中多次按请求启动。
4.4 专家 GEMM:两种布局,相同的行结果
保持不变。 路由专家的 FP8 分组 GEMM 主导 MoE 层,而两侧为它提供不同的布局。Rollout 使用 DeepEP 低延迟(LL)模式(原因:4.5),给出按专家分块、每块容量固定的掩码布局。训练用 DeepEP 普通模式处理大量 token,给出按专家打包的连续布局。
后端选择。 两个一致侧都使用批不变模式下的 DeepGEMM:它开源、有批不变模式,且是高性能部署内核。基线的 FlashInfer TRT-LLM MoE 以预编译 cubin 形式发布,因此我们无法修改它,也无法验证它是否批不变;cutlass 缺少 block-FP8,triton 落入一种未声明批不变性的批处理实现。单个专家 GEMM 在两者中耗时大致相同;基线更快是因为 TRT-LLM MoE 在多流流水线中将通信与计算重叠。
两种布局得到相同的行结果。 必须满足三个条件:
- 专家收到相同的 FP8 输入。 LL 在分发时量化,普通模式在其后量化,最初使用不同的舍入;我们将两者统一为逐元素就近舍入。
- 每一行的结果与布局无关。 量化按 token 进行,且批不变的 DeepGEMM 以固定方式累加每一行,因此一行的结果不依赖于其邻居。按位路由器 + MoE 测试证实了这一点。
- 激活只舍入一次。 两个 GEMM 之间的 SiLU(gate) × up 以 FP32 计算,只有乘积舍入为 BF16,然后量化为 FP8。在相乘前将 SiLU 舍入为 BF16 可能相差一个 ulp,而 FP8 量化会将其放大为码字翻转。

同一个 token(橙色)在 rollout 的掩码布局和训练的连续布局中处于不同位置,但三个条件使其专家 GEMM 结果按位完全相同。灰色:空槽;斜线:对齐填充。
不需要 R3。 路由器的评分 GEMM 在按位相同的输入上、对每种批大小都使用同一个 FP32 输出内核,因此重新计算的路由与 rollout 匹配。这假设每步 1 次更新(第 2 节)。
成本。 Rollout 放弃了 TRT-LLM MoE,但 GEMM 并非差距的主要部分。在推理基准(GB200,DP8/EP8,256 并发,每请求 150 输入 / 3000 输出)上,一致版本启动时比基线慢 33%,其中约 83% 来自不可重叠的 LL 通信(4.5)。训练为反向传播保存中间结果,并填充每个专家的行。下面的推理基准数字来自不同的优化阶段;只能在同一个句子内比较它们。
在比较性能之前,还要确认基线运行的是哪个内核:只要 vLLM 的共享库存在,它就会启用批不变的 SiLU·mul·量化内核,忽略一致性开关,因此任何附带该库的镜像在基线中也会运行它。
4.5 跨 GPU 归约:不在比较路径上
问题。 Rollout 想要低延迟并使用 DP8/EP8;训练必须容纳长序列激活并使用 EP32 加 CP4。如第 2 节所述,不同布局需要结果对并行度不变,通常通过固定且昂贵的归约树实现。
选择。这个方案将跨 GPU 归约排除在被比较的数值路径之外,而其余通信只移动数据、不改变数值:
- TP=1。线性层没有 all-reduce。vLLM 的 all-reduce 会根据消息大小选择算法,因此 TP 会重新引入这种依赖;该方案在启动时拒绝 TP>1。
- EP 合并是局部的。all-to-all 只移动数据。专家输出返回到拥有该 token 的 rank,并按 top-k 槽位顺序以 FP32 累加,与 EP=8 还是 32、或哪个 rank 先应答无关。
- CP 不做归约。相邻 rank 只交换边界隐藏状态,压缩后的 KV 在 CP 组内做 all-gather:是拼接,不是求和。

一个 token 的路径:all-to-all 只移动数据,唯一的求和发生在拥有该 token 的 GPU 上,以 FP32 按 top-k 槽位顺序进行,因此 EP8 和 EP32 给出相同结果。
让数据移动具有确定性。在 rollout 中,每个 rank 每次调用最多发送固定数量的 token,所有 rank 同步分段,因此通信形状是固定的。只有 LL 实现了这一点,而 DeepEP 普通模式在 DP>1 时会禁用 CUDA Graph,所以 rollout 必须使用 LL。在训练中,普通模式没有固定的到达顺序,因此路由元数据会被再分发一次,并用逐位哈希将每个专家输出匹配到其 token 和槽位。
成本与恢复。LL 模式每步运行 43 层 × 2 = 86 次串行 all-to-all,无法与计算重叠。我们尝试过的跨层重叠方案会在输入就绪前进行计算,破坏批次独立性。交错两个微批次(DBO)慢了 39%:每层的专家权重被读取两次,而权重读取已经占用了 84% 的 HBM 带宽。
我们最终在推理基准上弥合了通信差距。首先确认两次运行使用相同的传输方式:我们损失了两天时间,因为一个脚本将一致性版本的 DeepEP 固定到以太网 RDMA(RoCE),而基线使用的是快得多的 MNNVL。在传输方式匹配、共享专家放在辅助流上之后,一致性版本比基线快 3.2%(16834 对 16313 tok/s)。
TP>1 和解码上下文并行会将跨 GPU 归约重新放回被比较的路径上,目前尚不支持。
4.6 CUDA Graph:保留图并使其与一致性共存
为什么要保留它们。解码时每个请求一个 token,每层每步运行数十个小内核,因此启动开销会占主导;我们保留 CUDA Graph 以达到性能目标。图与逐位一致性在三处发生冲突。

每种模式对三类步骤所走的路径,右侧是推理基准吞吐量。一个请求会根据其批次在图和 eager 之间切换,因此两条路径必须逐位相同。
冲突 1:图还是 eager。如果某一步的总 token 数适合最大捕获大小,则该步在图里运行,否则在 eager 中运行,这取决于该步中的其他 token,因此两条路径必须逐位相同。这是我们的第一个不匹配:一个索引器短上下文捷径在捕获时根据虚拟输入的长度被固化到图中。让检查只依赖于静态上界就修复了它。
模式也很重要。PIECEWISE 无法捕获注意力,而 DeepSeek-V4 上的 FULL 只捕获纯解码步骤。在推理基准测试中,FULL_AND_PIECEWISE 达到 16409 tok/s,FULL 为 16068,PIECEWISE 为 10423;FULL 在 eager 模式下每个步骤都与 prefill 混合执行,将首 token 时间从 764 ms 提高到 1792 ms。我们使用 FULL_AND_PIECEWISE:纯解码步骤使用完整图,混合步骤使用分段捕获;在 100 步运行中 diff 保持为 0。
冲突 2:图中不能有主机同步或依赖批次的形状。这与一致性相符,许多设计同时满足两者:MoE 调度提示使用静态容量,而不是将每个专家的 token 计数读回主机(4.4);LL 固定通信形状(4.5);工作区仅按模型限制确定大小,在捕获前分配然后锁定;仅用于性能的快捷分支在捕获时被禁用。
冲突 3:图捕获地址,而权重每一步都在变化。Rollout 必须将新权重复制到其原始存储中,并就地重新计算权重派生参数,而不是分配新内存使图停留在旧地址上。仅在第一步匹配是不够的,因此该技能在更新、导出和重新加载后再次进行比较。
成本。按上限预留的工作区会消耗一些内存。其他速度恢复措施(移除 rollout 中不必要的调试填充和冷启动、更快的 KV 缓存清零)对两次运行都适用;基线也必须获得这些措施,否则性能损失数字毫无意义。
5. 用 Agent + 技能降低工程成本
A 和 B 可以共存,但第 4 节的主表及其中的关键选择需要专家判断:哪个后端是批次不变的,离散选择需要对齐的平局和顺序,如何在不同通信模式下对齐量化和合并顺序,以及通信差距首先需要排除传输差异。新模型、精度或硬件意味着从头开始,因此 C 不成立。
在 DeepSeek-V4-Flash 之后,我们看到大部分工作分为两类,每类都有可衡量的目标:
- 将 diff 降至 0。固定权重和输入,找到双方出现分歧的第一个算子,将其缩减为可重新运行的最小复现,修复它,然后在真实训练中验证。输出直接比较。
- 在保持 diff 为 0 的同时优化性能。分析热点,对单个算子进行 A/B 测试,并在每次更改后重新验证一致性。改变结果或仅在微基准测试中显现、而非真实 rollout 中的加速不算数;不允许通过减慢基线来缩小差距。
阅读代码、追踪调用链、定位分歧、编写复现、修改内核并重新运行回归是适合 agent 的长期工作,成功与否由一致性比较和性能测量决定。
我们将其打包为 train-inference-consistency 技能:它告诉 agent 按什么顺序做什么,以及每一步必须达到什么才能通过,因此该过程可以沿用到其他模型。
该技能有 11 个门(C0–C10),具有明确的通过条件。为了加速冷启动,我们首先从完整模型中切出几层形成一个小模型,在那里解决大部分问题,然后转向完整模型。五个步骤:
- 设定目标和基线(C0–C1)。 列出模型的各个模块及其精度和并行方式。将普通的、非一致性版本(基线)调优到正常的高性能,供人工审查。它是“一致性版本慢多少”的分母。
- 在小模型上让推理自洽(C2–C4)。 小模型(通常从完整模型中选取 4–6 层)必须包含所有算子类别,保持相邻层交互,并匹配每层大小和量化,从而复现大多数局部数值问题。完整深度、长上下文、多 GPU 布局和内存压力仍必须在完整模型上被接受。首先让推理引擎内部自洽:单个请求单独运行和批量运行结果相同,整输入处理与逐 token 处理一致。同时,寻找会拖慢一致性版本的结构性问题。
- 连接训练(C5–C7)。 训练调用与推理相同的算子,重新计算推理生成的数据,并逐 token 比较;出现不一致时,逐层比较以找到第一个发散层。然后加入反向传播,并对照独立于被测代码的参考实现检查梯度:前向一致并不意味着梯度正确。
- 运行 RL,然后切换到完整模型(C8–C9)。 在小模型上,在真实训练框架中连续运行两次更新,在每次更新前后相同权重版本下进行比较。人工审查后,切换到完整模型并逐步扩大规模,包括检查 checkpoint 恢复后的第一步。
- 恢复性能(C10)。 逐个优化最昂贵的算子,每次重新检查一致性,然后在交付镜像上与基线进行配对比较。
有两个设计最重要。第一,两个 agent 并行推进基线和一致性:一个让基线变快,另一个构建一致性版本。调优后的基线被冻结以供人工审查,因此 agent 无法为了缩小差距而拖慢它。第二,我们的坑被写入流程:运行的程序必须与所读的源码一致;checkpoint 需要验证恢复后的第一步,而不仅仅是保存;每个性能结论都需要公平的基线和可追溯的原始数据;在比较通信瓶颈之前,确认两次运行使用相同的传输方式(4.5 的教训)。
该技能降低了定位和回归的成本(此处尚未量化节省量);它不能替代人工判断:人类仍需审查基线、精度权衡和配方。一个例子是每步多次更新时的路由漂移:agent 根据症状构建了小规模复现,运行了判别性实验并排除了假设,最终由人类决定改为每步 1 次更新。
6. 迁移:Nemotron,一种 Mamba 混合架构 + NVFP4
随后我们在一个结构上非常不同的模型上测试了参考配方和技能。
Nemotron-3.5-Lightning-30B-A3B (NVFP4) 是 GB200 上的 Mamba-2 / attention / MoE 混合架构(NVFP4 W4A16 路由专家、FP8 Mamba 投影、attention 的 FP8 KV cache)。基线使用 NVFP4 QAT 训练(量化规则和导出均来自 NVIDIA ModelOpt)+ R3 路由重放;一致性版本共享 vLLM 的前向,不使用路由重放。两次运行都使用 BF16 主权重,并且每步 1 次更新。
循环结构已有先例。在 vLLM 社区中,Alexander Jiang 和 SkyRL 团队在 IsoExec 中为 Qwen3.5-35B-A3B 的 Gated DeltaNet (GDN) 设计了分块并行循环 (CPR):它保留循环形式,但以分块并行方式计算,因此训练、预填充和解码共享同一种舍入顺序,其统一模型为训练和推理提供了一致的模型定义和一组对齐的内核。IsoExec 的“零不匹配”在其执行契约内成立;端到端它仍报告一个微小的非零 logprob 差异(平均绝对差约 1e-7),这与本文的“逐位相同”不同。Mamba-2 是同类的线性循环。我们让解码精确重放预填充的分块算术,而不是其自身的单步循环,并且训练导入与推理相同的算子,如同统一模型一样。Agent + skill 完成了这种对齐。
在 vLLM 中,每个可能依赖于批次组成的算子都获得了一个批次不变的实现,并且训练(一个轻量级的 Megatron-LM 分支)导入它们:
| 算子 | 一致版本 |
|---|---|
| Mamba-2 SSD 扫描 | 解码精确重放预填充的分块算术,固定自动调优配置,fp32 状态 |
| 注意力 | FlashAttention 使用固定的 split-KV,分割数与批次无关 |
| 路由专家(NVFP4 W4A16:4 位权重,16 位激活) | CuTe-DSL 分组 GEMM,单一策略,无 split-K,按槽位顺序进行 fp32 合并 |
| MoE 全对全 | 单向通信,固定合并顺序 |
| 路由器 GEMM | 批次不变的持久化矩阵乘法,fp32 输出 |
| 归一化 / FP8 量化 | 统一的批次不变内核;禁止改变值的融合 |
结果:在 SWE-bench Pro 上训练
我们在远程沙箱中对 SWE-bench Pro 运行智能体强化学习:每步 32 个任务 × 8 条轨迹,每条轨迹平均约 57K 个 token,GRPO,每次运行 8 个 GB200 节点。训练集是 375 个 SWE-bench Pro 任务,在这些任务上,基础模型的 4 次采样尝试既不是全对也不是全错,并且黄金补丁在我们的沙箱中通过,因此下面的分数是在该子集上的训练奖励。两次运行使用相同的镜像和配方,没有 TIS 或 MIS 等修正。每步 1 次更新时,更新前 rollout logprobs 与训练侧重新计算之间的 KL 就是该步的训练-推理 KL。

顶部:每步训练-推理 KL;中部:训练分数(当前步和前 4 步的移动平均);底部:actor 熵。橙色:基线;蓝色:一致版本。在一致版本的第 10 步,只有少数轨迹从沙箱返回,因此其分数被排除。
- 一致版本:训练-推理 KL 每一步都恰好为 0,熵保持在 0.05 左右,分数从 0.56(前 10 步)升至 0.59(后 10 步),没有下降。
- 基线:训练-推理 KL 从约 0.015 降至约 0.002,但熵从 0.05 升至 2.6,分数从 0.56 降至 0.22(前 10 步与后 10 步相比)。
- rollout 速度:在前 30 步中,当两次运行都健康时,一致版本的 rollout 吞吐量是基线的 87.5%,仅慢 12.5%。

每步 rollout 吞吐量(token/ms,当前步和前 4 步的移动平均)。橙色:基线;蓝色:一致版本。
7. 开源
我们将此开源,以便其他人能够以更少依赖资深工程师逐算子检查的方式实现训练-推理一致性,利用该技能、现成代码以及在他们自己模型上的 agent。发布内容:
- train-inference-consistency skill:完整工作流(C0–C10)、十份指南,以及 logprob 对比和梯度交叉检查等关键实践;
- DeepSeek-V4:一个真正的 on-policy RL 配方,在 Megatron 和 vLLM 上达到 0 diff,verl-project/verl#7688;
- Nemotron-3.5-Lightning-30B-A3B (BF16 master weights, quantized to the deployment NVFP4 in the training forward and at weight sync):
- 与训练对齐的 vLLM 位级一致推理:aoshen02/vllm#85;
- 与 vLLM 对齐的训练 actor:aoshen02/Megatron-LM#7;
- 两处 verl 改动:aoshen02/verl#11;
- 训练-推理一致性配方和统一镜像 Dockerfile:aoshen02/verl#12。
欢迎提交 issue、PR,以及在你自己的模型上使用该技能后的反馈。
下一步:DeepSeek-V4.1 的训练-推理一致性已达到 0 diff;性能优化正在进行中,并将单独发文。
致谢
感谢(组织按字母顺序排列):
来源:vLLM Blog · vllm.ai