Prhub

#34159 Fix deterministic inference all-reduce for tp>1

原始 PR 作者 ispobock 合并时间 2026-08-09 18:08 文件变更 2 提交数 1 评论 4 代码增减 +11 / -1

执行摘要

TP>1 确定性推理固定 NCCL channel 数,消除 prefill/decode 差异

PR body 指出:--enable-deterministic-inference 仅将 NCCL 算法固定为 allreduce:tree 并禁用自定义/symmetric-memory all-reduce,这只保证“对于给定消息大小可复现”,并不保证“不随消息大小变化”:NCCL 会根据消息大小挑选 channel 数,而每个 channel 携带形状不同的树,因此同一元素在不同 batch 大小下归约顺序不同。结果 prefill 与 decode 对同一 token 的 logprob 仍可能不一致。作者明确说明“Pinning the channel count closes that gap.”(固定 channel 数可消除该差异)。

该 PR 值得精读,尤其适合对 NCCL 确定性机制感兴趣的工程师:它揭示了“算法固定”不等于“消息大小无关”这一深层细节,以及用 NCCL_MIN/MAX_NCHANNELS 同时 pin 来消除 channel 数影响的做法。建议后续补充针对 channel pin 的专项回归测试,并在文档中给出 SGLANG_DETERMINISTIC_NCCL_NCHANNELS 的调参指引。

讨论亮点

该 PR 没有进入人工 review 讨论(review 评论数为 0),评论区只有 CI bot 的重跑结果。作者两次发起 /rerun-test registered/attention/test_deterministic.pyregistered/8-gpu-models/test_inkling_nvfp4_nightly.py1-gpu-h1008-gpu-h200 均通过,而 8-gpu-b200 连续两次失败。失败原因未在评论中解释,PR 最终在带 bypass-fastfail 标签的情况下被合并。

实现拆解

变更分两步,全部集中在启动配置路径:

  1. 新增环境变量python/sglang/srt/environ.py):在 Envs 类的 deterministic inference 分组下新增 SGLANG_DETERMINISTIC_NCCL_NCHANNELS = EnvInt(8),默认 8,并添加注释说明其用途——固定 channel 数以保证同一 token 的归约顺序与 batch 无关,同时允许用户调大以换回更多带宽。
  2. 启动时 pin channelpython/sglang/srt/server_args.py):在 _handle_deterministic_inference 的 CUDA 分支(既有 NCCL_ALGO=allreduce:treedisable_custom_all_reduce = Trueenable_torch_symm_mem = False 之后)读取该环境变量,并同时写入 NCCL_MIN_NCHANNELSNCCL_MAX_NCHANNELS,将 NCCL channel 数固定为同一值。AMD/HIP 分支不变,因为 1-stage all-reduce 本身已天然确定性。
  3. 同步更新日志:将 warning 文案改为提示 channel 数已被 pin,便于用户在日志中确认生效状态。
  4. 测试配套:未新增单测文件,依赖现有 registered/attention/test_deterministic.pyregistered/8-gpu-models/test_inkling_nvfp4_nightly.py 做回归验证;PR 评论中针对这两个测试发起了 /rerun-test
文件 模块 状态 重要度
python/sglang/srt/server_args.py 启动配置 modified 5.67
python/sglang/srt/environ.py 环境变量 modified 5.23

关键符号

_handle_deterministic_inference

关键源码片段

python/sglang/srt/server_args.py core-logic

核心修复点:在 `_handle_deterministic_inference` 的 CUDA 分支中将 `NCCL_MIN_NCHANNELS` 与 `NCCL_MAX_NCHANNELS` 固定为同一值,消除 NCCL 因消息大小选择不同 channel 数带来的归约顺序差异。

# python/sglang/srt/server_args.py(head 版本,重组展示核心分支)# 该函数在 --enable-deterministic-inference 开启时被调用,负责配置 NCCL 相关环境。
def _handle_deterministic_inference(self):
    # ... 前面的注意力后端 / radix cache 检查省略 ...
​
    # 仅 TP 规模大于 1 时,all-reduce 才会成为精度不确定性来源
    if self.tp_size > 1:
        if is_hip():
            # AMD:使用 1-stage all-reduce kernel,各 GPU 固定顺序读取全量数据,
            # 天然确定性,无需额外 pin channel 数。
            logger.info("AMD/ROCm: Using 1-stage all-reduce kernel (deterministic)")
        else:
            # CUDA:固定 NCCL 树算法,并禁用自定义 / symmetric-memory all-reduce
            os.environ["NCCL_ALGO"] = "allreduce:tree"
            self.disable_custom_all_reduce = True
            # symmetric-memory 路径存在字节阈值,会随 token 数切换,破坏确定性
            self.enable_torch_symm_mem = False
​
            # 关键修复:NCCL 默认按消息大小选择 channel 数,而每个 channel 的树形状不同,
            # 同一元素在 prefill / decode 不同 batch 大小下归约顺序不同。
            # 这里将 channel 数上下限同时固定为环境变量指定值(默认 8),
            # 使归约顺序与 batch 中的 token 数无关,达到 bit 级一致。
            nchannels = str(envs.SGLANG_DETERMINISTIC_NCCL_NCHANNELS.get())
            os.environ["NCCL_MIN_NCHANNELS"] = nchannels
            os.environ["NCCL_MAX_NCHANNELS"] = nchannels
​
            logger.warning(
                "NCCL_ALGO is set to 'allreduce:tree', the NCCL channel count is "
                "pinned, and custom and symmetric-memory all reduce are disabled "
                "for deterministic inference when TP size > 1."
            )
python/sglang/srt/environ.py core-logic

新增 `SGLANG_DETERMINISTIC_NCCL_NCHANNELS` 环境变量,默认 8,为 channel pin 提供可调入口,直接决定 `server_args.py` 中写入的值。

# python/sglang/srt/environ.py(head 版本,重组展示 deterministic 分组)class Envs:
    # ... 其他环境变量省略 ...
​
    # Deterministic inference 相关开关
    SGLANG_ENABLE_DETERMINISTIC_INFERENCE = EnvBool(False)
    # AMD 平台上使用 1-stage all-reduce kernel(确定性、固定累加顺序)
    SGLANG_USE_1STAGE_ALLREDUCE = EnvBool(False)
​
    # CUDA 上固定 NCCL channel 数,使 all-reduce 对同一元素的归约顺序
    # 不随 batch 中的 token 数变化,prefill 与 decode 结果 bit 级一致。
    # 默认 8;在链路带宽允许时可以调大,以换回更高的 all-reduce 吞吐。
    SGLANG_DETERMINISTIC_NCCL_NCHANNELS = EnvInt(8)
​
    SGLANG_OPT_USE_CUSTOM_ALL_REDUCE_V2 = EnvBool(True)
    # ... 后续配置省略 ...

评论区精华

CI 重跑验证与 B200 失败 other

作者通过 `/rerun-test` 请求重跑 `registered/attention/test_deterministic.py` 与 `registered/8-gpu-models/test_inkling_nvfp4_nightly.py`;bot 回复显示 h100 与 h200 通过,b200 连续两次失败。

结论:B200 失败原因未在评论区解释,PR 最终在带 `bypass-fastfail` 标签的情况下合并。 · unresolved

风险与影响

主要风险如下:

  • 缺少直接回归测试:没有新增针对 channel pin 的专项测试,现有 test_deterministic.py 和 nightly Inkling 测试是间接覆盖;如果未来有代码改动 NCCL 环境变量初始化顺序,可能回归且不易察觉。
  • 性能风险:将 NCCL_MIN_NCHANNELSNCCL_MAX_NCHANNELS 同时 pin 为 8,会禁用 NCCL 根据实际链路自动选择 channel 数;在支持更多通道的高带宽拓扑上可能损失 all-reduce 带宽。环境变量默认 8 是一个“够用”但未必最优的值。
  • B200 CI 未通过test_inkling_nvfp4_nightly.py8-gpu-b200 上两次失败,未在评论区说明根因,可能是环境或 fp8 数值问题,需要关注后续 nightly 是否持续失败。
  • 仅消除 all-reduce 源:PR body 明确 Qwen3-Next 在 tp=4 下 KL 散度只从 6.44e-4 降到 5.06e-4,说明 batch_invariant_ops 之外的 kernel 仍可能引入偏差,用户不应期望所有模型都达到 bit-identical。
  • 环境变量全局生效os.environ 的修改作用于整个 server 进程,会影响该进程内所有后续 NCCL 通信;但由于只在 deterministic 模式下设置,普通推理不受影响。

影响范围集中在“确定性推理”这一实验性功能:

  • 用户侧:启用 --enable-deterministic-inference 且 tp>1 的用户(如 Inkling 复现、KL 校验场景)获得 prefill 与 decode 一致的 logprob,这是核心收益。
  • 性能侧:仅 deterministic 模式用户可能因 channel pin 而略降 all-reduce 带宽,可通过调大 SGLANG_DETERMINISTIC_NCCL_NCHANNELS 换回。普通推理路径完全不受影响。
  • 团队侧:修复了确定性推理的一项正确性缺陷,使依赖 all-reduce 可复现性的测试与诊断更可靠;同时提供了一个可调性能开关,便于后续在“确定性”与“带宽”之间折中。
  • 影响程度:低到中。改动仅 2 个文件、净增 11 行,且集中在启动配置初始化路径。
缺少直接回归测试 默认 channel 数可能降低带宽 B200 CI 未通过 仅消除 all-reduce 不确定性源

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论