执行摘要
- 一句话:TP>1 确定性推理固定 NCCL channel 数,消除 prefill/decode 差异
- 推荐动作:该 PR 值得精读,尤其适合对 NCCL 确定性机制感兴趣的工程师:它揭示了“算法固定”不等于“消息大小无关”这一深层细节,以及用
NCCL_MIN/MAX_NCHANNELS 同时 pin 来消除 channel 数影响的做法。建议后续补充针对 channel pin 的专项回归测试,并在文档中给出 SGLANG_DETERMINISTIC_NCCL_NCHANNELS 的调参指引。
功能与动机
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 数可消除该差异)。
实现拆解
变更分两步,全部集中在启动配置路径:
- 新增环境变量(
python/sglang/srt/environ.py):在 Envs 类的 deterministic inference 分组下新增 SGLANG_DETERMINISTIC_NCCL_NCHANNELS = EnvInt(8),默认 8,并添加注释说明其用途——固定 channel 数以保证同一 token 的归约顺序与 batch 无关,同时允许用户调大以换回更多带宽。
- 启动时 pin channel(
python/sglang/srt/server_args.py):在 _handle_deterministic_inference 的 CUDA 分支(既有 NCCL_ALGO=allreduce:tree、disable_custom_all_reduce = True、enable_torch_symm_mem = False 之后)读取该环境变量,并同时写入 NCCL_MIN_NCHANNELS 与 NCCL_MAX_NCHANNELS,将 NCCL channel 数固定为同一值。AMD/HIP 分支不变,因为 1-stage all-reduce 本身已天然确定性。
- 同步更新日志:将 warning 文案改为提示 channel 数已被 pin,便于用户在日志中确认生效状态。
- 测试配套:未新增单测文件,依赖现有
registered/attention/test_deterministic.py 与 registered/8-gpu-models/test_inkling_nvfp4_nightly.py 做回归验证;PR 评论中针对这两个测试发起了 /rerun-test。
关键文件:
python/sglang/srt/server_args.py(模块 启动配置;类别 source;类型 core-logic;符号 _handle_deterministic_inference): 核心修复点:在 _handle_deterministic_inference 的 CUDA 分支中将 NCCL_MIN_NCHANNELS 与 NCCL_MAX_NCHANNELS 固定为同一值,消除 NCCL 因消息大小选择不同 channel 数带来的归约顺序差异。
python/sglang/srt/environ.py(模块 环境变量;类别 source;类型 core-logic;符号 SGLANG_DETERMINISTIC_NCCL_NCHANNELS): 新增 SGLANG_DETERMINISTIC_NCCL_NCHANNELS 环境变量,默认 8,为 channel pin 提供可调入口,直接决定 server_args.py 中写入的值。
关键符号:_handle_deterministic_inference
关键源码片段
python/sglang/srt/server_args.py
核心修复点:在 _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
新增 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)
# ... 后续配置省略 ...
评论区精华
该 PR 没有进入人工 review 讨论(review 评论数为 0),评论区只有 CI bot 的重跑结果。作者两次发起 /rerun-test registered/attention/test_deterministic.py 与 registered/8-gpu-models/test_inkling_nvfp4_nightly.py:1-gpu-h100 与 8-gpu-h200 均通过,而 8-gpu-b200 连续两次失败。失败原因未在评论中解释,PR 最终在带 bypass-fastfail 标签的情况下被合并。
- CI 重跑验证与 B200 失败 (other): B200 失败原因未在评论区解释,PR 最终在带
bypass-fastfail 标签的情况下合并。
风险与影响
-
风险:主要风险如下:
-
缺少直接回归测试:没有新增针对 channel pin 的专项测试,现有 test_deterministic.py 和 nightly Inkling 测试是间接覆盖;如果未来有代码改动 NCCL 环境变量初始化顺序,可能回归且不易察觉。
- 性能风险:将
NCCL_MIN_NCHANNELS 与 NCCL_MAX_NCHANNELS 同时 pin 为 8,会禁用 NCCL 根据实际链路自动选择 channel 数;在支持更多通道的高带宽拓扑上可能损失 all-reduce 带宽。环境变量默认 8 是一个“够用”但未必最优的值。
- B200 CI 未通过:
test_inkling_nvfp4_nightly.py 在 8-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 不确定性源
关联脉络
- PR #32402 Switch inkling per-commit test to nvfp4: 该 PR 引入 Inkling-Small-NVFP4 的 CI 精度测试,而本次 PR 的验证数据(KL 散度 0.151 降至 0)正是基于该模型,二者共享相同的回归与 nightly 测试路径。
参与讨论