Prhub

#34459 Fix DeepSeek-V4 routing: sqrtsoftplus underflow and unfloored renorm

原始 PR 作者 b8zhong 合并时间 2026-09-10 15:10 文件变更 4 提交数 6 评论 0 代码增减 +23 / -7

执行摘要

修复 DeepSeek-V4 路由器 sqrtsoftplus 下溢和权重归一化除零,防止专家权重 NaN。

DeepSeek-V4 的 sqrtsoftplus 门控有四种路由实现,每种都缺失了参考门控(inference/model.py Gate.forward,使用 F.softplus)提供的一部分数值保护。关联的 issue #30989 和 flashinfer-ai/flashinfer#3803 报告了类似的 NaN 问题。具体而言:

1) Triton 路由器(默认,第 3-42 层)在 fp32 下,当 x <= -16.64 时,log(1.0 + exp(x)) 会下溢为 0。若所有 top-K 专家都低于此阈值,行权重归一化后为全零,令牌将丢失其整个路由专家贡献。
2) hash_topk(第 0-2 层)在 CUDA 内核(默认)及其 torch 回退中,除以 routed_sum 时没有添加 epsilon 保护。哈希路由忽略分数,因此全下溢行会导致 0/0 = NaN。

该 PR 是一个高质量的、针对性的 bugfix,解决了可能导致生产事故的严重数值问题。作者在 PR body 中提供了极其详尽的技术分析,展示了对问题的深刻理解。由于其修复了核心路由路径的数值稳定性,且变更涉及 Triton/CUDA 内核,强烈建议所有关注模型推理稳定性和 MoE 路由正确性的工程师精读。特别关注其实现的 log1p 数学技巧和归一化 epsilon 的添加方式。虽然没有讨论线程,但其技术方案清晰合理。

讨论亮点

PR 的 review 过程极为简洁,仅有一条批准记录,没有公开的讨论线程。主要技术洞察和决策都体现在作者的 PR body 和提交信息中,其中详细阐述了问题根因、数学原理和修复方案。这表明变更的正确性主要通过代码审查和作者的专业知识来保证,而非通过激烈的线上讨论。

实现拆解

变更在四个文件中修复了两类核心数值缺陷。

  1. Triton 路由器 softplus 数值稳定性修复:修改 python/sglang/kernels/ops/moe/moe_fused_gate.py 中的 Triton 内核 _router_triton_kernel。将原先不稳定的 tl.log(1.0 + tl.exp(scores)) 计算,替换为使用 log1p 数学技巧的精确计算:tl.where(exact, z, z * tl.log(u) / tl.where(exact, 1.0, u - 1.0)),其中 z = exp(-abs(x)), u = 1 + z,并结合 tl.maximum(scores, 0.0) 进行符号折叠。此修改确保了在 [-104, 200] 全范围内,相对误差从 1.0 降低到 3.5e-07。

  2. 归一化 epsilon 添加:在以下三处添加了极小的归一化 epsilon,以防止除零:
    a. hash_topk 的 torch 回退:修改 python/sglang/srt/layers/moe/hash_topk.py 的 _forward_torch 方法,在两处 topk_weights 归一化除法中,为分母添加 _RENORMALIZE_SUM_EPSILON(从 sglang.srt.layers.moe.topk 导入)。这覆盖了 num_fused_shared_experts == 1 和 != 1 两种分支。
    b. hash_topk 的 CUDA 内核:修改 python/sglang/kernels/jit/csrc/deepseek_v4/hash_topk.cuh,在 moe_hash_topk_fused 内核中,将权重归一化从 routed_weight / routed_sum 改为 routed_weight / (routed_sum + kRenormalizeSumEpsilon),其中 kRenormalizeSumEpsilon = 1e-20f。
    c. CUDA moe_fused_gate 的 softplus 修复:修改 python/sglang/kernels/jit/csrc/moe/moe_fused_gate.cuh 中的 compute_score 函数,将 log1pf(expf(x)) 替换为与 Triton 内核相同的符号折叠公式:fmaxf(x, 0.0f) + log1pf(expf(-fabsf(x))),以解决溢出问题。

本 PR 仅包含核心逻辑修改,未涉及测试文件变更,但 PR body 声明已通过 test_moe_fused_gate.py 的 39 个测试。

文件 模块 状态 重要度
python/sglang/kernels/ops/moe/moe_fused_gate.py MoE 路由 modified 4.69
python/sglang/srt/layers/moe/hash_topk.py MoE 路由 modified 5.6
python/sglang/kernels/jit/csrc/deepseek_v4/hash_topk.cuh MoE 路由 modified 3.64
python/sglang/kernels/jit/csrc/moe/moe_fused_gate.cuh MoE 路由 modified 3.12

关键符号

HashTopK._forward_torch compute_score moe_hash_topk_fused

关键源码片段

python/sglang/kernels/ops/moe/moe_fused_gate.py core-logic

包含默认 Triton 路由器的核心逻辑,是 `sqrtsoftplus` 计算的主要修复点,修复了下溢和溢出问题。

# 修复前:在 Triton 路由器内核中,log(1.0 + exp(x)) 在 x 较小时下溢,在 x 较大时溢出。
# sp = tl.where(scores > 20.0, scores, tl.log(1.0 + tl.exp(scores)))
​
# 修复后:使用 log1p 的精确数学技巧计算 softplus,并进行符号折叠以避免大负数下溢和大正数溢出。
# log1p(z) = z*log(u)/(u-1) where z=exp(-|x|), u=1+z。
# 这样计算精确且数值稳定,在 [-104, 200] 范围内最大相对误差仅为 3.5e-07。
z = tl.exp(-tl.abs(scores))
u = 1.0 + z
exact = u == 1.0
log1p_z = tl.where(exact, z, z * tl.log(u) / tl.where(exact, 1.0, u - 1.0))
sp = tl.maximum(scores, 0.0) + log1p_z
activated = tl.sqrt(sp)
python/sglang/srt/layers/moe/hash_topk.py core-logic

包含 `hash_topk` 的 torch 回退实现,修复了归一化除零导致 NaN 的问题。

# 修复前:直接除以路由权重之和,当和为 0 时会导致 0/0 = NaN。
# topk_weights[:, :-1] /= topk_weights[:, :-1].sum(dim=-1, keepdim=True)
​
# 修复后:在分母中加入一个极小的 epsilon (_RENORMALIZE_SUM_EPSILON = 1e-20),防止除零。
# 这个 epsilon 非常小,对正常情况(sum >> epsilon)的结果没有影响,仅在 sum 接近零时提供保护。
if self.score_func != "softmax":
    topk_weights[:, :-1] /= (
        topk_weights[:, :-1].sum(dim=-1, keepdim=True)
        + _RENORMALIZE_SUM_EPSILON
    )
python/sglang/kernels/jit/csrc/deepseek_v4/hash_topk.cuh core-logic

包含 `hash_topk` 的 CUDA 内核实现,修复了归一化除零问题,与 torch 回退逻辑保持一致。

// 定义与 Python 侧对应的极小 epsilon 常量,用于归一化保护。
constexpr float kRenormalizeSumEpsilon = 1e-20f;
​
// ... 在内核函数 moe_hash_topk_fused 中 ...
// 修复前:直接除以 routed_sum,可能为 0。
// topk_weights[output_offset] = is_shared ? 1.0f / routed_scaling_factor : routed_weight / routed_sum;
​
// 修复后:在分母中添加 epsilon,防止除零。对正常情况无影响。
topk_weights[output_offset] = 
    is_shared ? 1.0f / routed_scaling_factor : routed_weight / (routed_sum + kRenormalizeSumEpsilon);

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. 数值精度回归风险:修改了核心的 softplus 计算和归一化逻辑。虽然数学上与参考实现 F.softplus 对齐,且声明通过了现有测试,但涉及广泛的模型层(第 0-42 层),任何实现细微偏差都可能影响模型输出质量或训练稳定性。需要持续监控生产环境中的模型行为。
  2. 硬件兼容性风险:CUDA 和 Triton 内核被修改,影响了 GPU 后端。尽管代码本身是平台无关的,但不同 GPU 架构(如 Blackwell B300)的浮点行为可能存在细微差异,可能在特定边界条件下触发未预料到的问题。
  3. 测试覆盖不足风险:PR 未添加新的针对性单元测试来验证这些特定的数值边界条件(如输入为 -200、+200)。现有的测试(如 test_moe_fused_gate.py)可能覆盖不全,存在回归风险。
  1. 用户影响:直接影响所有使用 DeepSeek-V4 模型(具有 sqrtsoftplus 门控)的用户。修复了可能导致长上下文或特定输入下输出崩溃(NaN、全零专家权重)的严重 bug,提升了模型服务的稳定性和可靠性。
  2. 系统影响:影响 SGLang 的 MoE 路由子系统,特别是默认的 Triton 路由器和 hash_topk 路径。是核心推理路径的关键数值修复。
  3. 团队影响:为团队提供了一个处理类似数值稳定性问题的参考案例。通过统一归一化 epsilon 的做法(复用 _RENORMALIZE_SUM_EPSILON),有助于代码库内的一致性和维护。
核心路径变更 数值精度敏感 缺少新增测试覆盖

关联 Issue

#3803 Fix 0/0 NaN in GLM52 routing renorm on sigmoid underflow
#30989 [Bug] GLM-5.2-NVFP4 + flashinfer_trtllm: long-context outputs collapse to '!!!!' (NaN logits) — fp32 correction_bias regression from #29783

完整报告

参与讨论