Prhub

#32953 [Fix] Restore online MXFP8 quantization for linear layers

原始 PR 作者 b8zhong 合并时间 2026-07-31 14:42 文件变更 2 提交数 4 评论 2 代码增减 +9 / -11

执行摘要

移除误加的 MXFP8 在线量化拦截,恢复线性层 bf16 量化

PR body 明确描述:任何 bf16 checkpoint 以 --quantization mxfp8 启动都会在模型初始化阶段失败,报错 MXFP8 requires fp8-serialized checkpoint for linear layers.,而在线量化路径本就是将 bf16 权重在 process_weights_after_loading 中量化;guard 在 #17449 时嵌套在 if is_checkpoint_fp8_serialized: 内不可达,#28291 的 create_fp8_weight_ 重构将其提升为 elif 后开始误触发。只有 linear 层受影响,MoE 路径从未有此 guard。

值得快速精读,特别是作为“重构引入回归”的诊断案例:guard 的可达性分析、重构提升作用域时的风险、以及量化路径统一在 process_weights_after_loading 处理权重 dtype 的设计原则。建议后续为在线 MXFP8 线性层初始化补充回归测试,并检查是否存在对旧属性 use_min_latency_fc1_gemm 的外部引用。

讨论亮点

该 PR 没有实质性的 review 评论线程。合并者 mmangkad 直接 APPROVED;AMD 方成员 fxmarty-amd 在 Issue 评论中表达感谢。Gemini Code Assist bot 仅发布其服务已停止的通知,不构成技术讨论。

实现拆解

  1. 移除 fp8.py 中的误拒分支:在 python/sglang/srt/layers/quantization/fp8.pyFp8LinearMethod.create_fp8_weight_ 中删除 elif use_mxfp8: raise ValueError(...) 分支。该分支是在 #28291 重构时从 if is_checkpoint_fp8_serialized: 内部被提升到 elif 的,原本不可达,提升后导致在线 MXFP8 量化路径在模型初始化阶段被拦截。删除后,bf16 权重继续走 process_weights_after_loading 完成到 float8_e4m3fn 的量化,与 MoE 路径行为保持一致。

  2. NemotronH 延迟 fused-A GEMM 资格判断:在 python/sglang/srt/models/nemotron_h.py 中,把 __init__ 里构造期计算的 use_min_latency_fc1_gemm 改为 _use_min_latency_fc1_gemm: bool | None = None,并在 _apply_fc1_latent_proj 首次调用时计算并缓存。原因是量化方法会在 process_weights_after_loading 中改写 fc1_latent_proj.weight 的 dtype,构造期检查看到的是非最终 dtype,可能导致 fused-A GEMM 资格误判。这是与主修复配套的必要调整。

  3. 同步与收尾:合并 main 分支(commit 205ca28)并删除过时注释(commit 1787991)。本次未新增测试文件,依赖既有 CI 验证;PR 主测试通过,Extra 测试失败且 PR 内未说明失败原因。

文件 模块 状态 重要度
python/sglang/srt/layers/quantization/fp8.py 量化层 modified 5.63
python/sglang/srt/models/nemotron_h.py 模型定义 modified 5.87

关键符号

create_fp8_weight_ Fp8LinearMethod.create_weights _apply_fc1_latent_proj process_weights_after_loading

关键源码片段

python/sglang/srt/layers/quantization/fp8.py core-logic

核心修复文件:删除 `create_fp8_weight_` 中对 `use_mxfp8` 在线路径的误拒绝分支,恢复 bf16 checkpoint 在线 MXFP8 量化能力。

# create_fp8_weight_ 是 Fp8LinearMethod 创建权重与 scale 参数的核心入口。
# 本片段聚焦 scale 参数注册的收尾逻辑以及在线量化路径的放行。
def create_fp8_weight_(layer, quant_config, use_mxfp8, output_partition_sizes,
                       weight_loader, is_checkpoint_fp8_serialized, ...):
    ...
    if is_checkpoint_fp8_serialized:
        # 序列化 checkpoint 路径:注册权重 scale 与输入激活 scale。
        # 权重 scale 统一走 PerTensorScaleParameter,并按 use_mxfp8 标记 ue8m0 格式。
        scale = PerTensorScaleParameter(
            data=torch.empty(len(output_partition_sizes), dtype=torch.float32),
            weight_loader=weight_loader,
        )
        scale.format_ue8m0 = use_mxfp8
        if scale_dtype != torch.uint8:
            scale[:] = torch.finfo(torch.float32).min
        layer.register_parameter('weight_scale_inv', scale)
​
        # 输入激活 scale:仅在静态 activation scheme 下注册,否则保持 None
        if (
            hasattr(quant_config, 'activation_scheme')
            and quant_config.activation_scheme == 'static'
        ) or (
            hasattr(quant_config, 'linear_activation_scheme')
            and quant_config.linear_activation_scheme == 'static'
        ):
            input_scale = PerTensorScaleParameter(
                data=torch.empty(len(output_partition_sizes), dtype=torch.float32),
                weight_loader=weight_loader,
            )
            input_scale[:] = torch.finfo(torch.float32).min
            layer.register_parameter('input_scale', input_scale)
        else:
            layer.register_parameter('input_scale', None)
    # 删除此前的“elif use_mxfp8: raise ValueError(...)”分支:
    # 在线 MXFP8 量化路径( bf16 checkpoint + --quantization mxfp8 )本应在
    # process_weights_after_loading 中完成权重转换,构造阶段不应拒绝。
python/sglang/srt/models/nemotron_h.py data-contract

配套修复:将 fused-A GEMM 资格判断从构造期延迟到首次 forward,避免权重在 `process_weights_after_loading` 被改写 dtype 后导致误判。

# NemotronH 的 fused-A GEMM 资格判断改为惰性求值。
# 构造阶段权重( bf16 )会被 process_weights_after_loading 改写为
# float8_e4m3fn 等量化 dtype,因此不能在 __init__ 里提前判定,必须推迟到
# 第一次 forward 看到最终 dtype 后再计算并缓存。
def _apply_fc1_latent_proj(self, hidden_states: torch.Tensor) -> torch.Tensor:
    if self._use_min_latency_fc1_gemm is None:
        self._use_min_latency_fc1_gemm = (
            self.use_latent_moe
            and self.fc1_latent_proj is not None
            and _is_cuda
            and fused_a_gemm_weight_eligible(self.fc1_latent_proj)
        )
    if self._use_min_latency_fc1_gemm:
        return linear_with_fused_a_gemm(self.fc1_latent_proj, hidden_states)
    return self.fc1_latent_proj(hidden_states)[0]

评论区精华

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

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

风险与影响

  • 缺少直接回归测试:没有新增针对 bf16 checkpoint + --quantization mxfp8 线性层初始化的单元测试,后续重构仍可能再次引入同类问题。
  • 报错位置延后:删除 guard 后,若出现异常配置组合(例如误设 use_mxfp8 但并无在线量化意图),错误可能延迟到 process_weights_after_loading 或推理阶段才暴露,定位成本更高。
  • 属性改名兼容性:nemotron_h.pyuse_min_latency_fc1_gemm 改为 _use_min_latency_fc1_gemm,若有外部代码引用旧属性名会触发 AttributeError,需要确认仓库内无其他引用。
  • 惰性求值缓存:NemotronH 的资格判断在首次 forward 时计算并缓存,若首次 forward 后权重被再次改写(少见),缓存结果可能过时。
  • PR Extra CI 失败未说明,需确认与本次改动无关联。

影响所有通过 --quantization mxfp8 使用在线量化的用户,尤其是 Blackwell 平台上的 bf16 checkpoint 部署:此前模型无法启动,修复后恢复预期行为,无需预转换 checkpoint。NemotronH 相关模型的首次 forward 多一次惰性判断,后续调用无额外开销。对团队而言,本次修复解决的是 #28291 引入的量化路径回归,恢复了用户与 CI 的预期。

核心量化路径变更缺少测试覆盖 旧属性名改名影响外部引用 报错位置延后 Extra CI 失败未说明

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论