Prhub

#23159 Revert "perf: optimize PCG inductor path for FP8 models (#21734)"

原始 PR 作者 bingxche 合并时间 2026-04-20 14:32 文件变更 2 提交数 2 评论 1 代码增减 +10 / -35

执行摘要

回滚对 PCG inductor 路径的 FP8 优化,修复 AMD CI 上的 GPU 内存访问错误。

PR #21734引入了一个在AMD CI上不可恢复的Memory access fault by GPU错误,导致7个以上测试失败,影响14个模型文件(包括bf16模型)。根本原因在于apply_qk_norm函数中,将q.reshape(-1, head_dim)k.reshape(-1, head_dim)替换为q.view(*q.shape[:-1], -1, head_dim)k.view(*k.shape[:-1], -1, head_dim)。在PCG捕获模式下,q/k张量经常是非连续的(来自QKV分割的stride-trick视图),.view()产生的张量其步长与下游q_norm/k_norm内核期望不符,导致内核写入未映射页,产生GPU内存访问错误。

此PR值得精读,因为它揭示了在异构硬件(特别是AMD GPU)上优化PyTorch计算图时,张量连续性和视图操作的微妙陷阱。关注点:

  1. 设计决策:在性能优化(使用.view()以保留张量结构)与兼容性(使用.reshape()确保内存布局安全)之间的权衡。
  2. 根本原因分析:PR body中详细的bisect和错误指纹分析,展示了如何定位跨平台兼容性问题。
  3. 后续行动:团队应考虑在回滚后,如何重新设计优化以避免类似问题,例如添加张量连续性检查或平台特定的优化路径。
讨论亮点

review评论较少,主要来自gemini-code-assist[bot]的自动化代码审查,指出此PR移除了与'inductor'编译器和全局服务器参数相关的条件逻辑,并简化了apply_qk_norm中的张量重塑(使用.reshape()替代.view())。HaiShaw批准了此PR。讨论焦点在于回滚的必要性已在PR body中详细论证,包括bisect结果、错误指纹和CI验证证据。

实现拆解

  1. 回滚apply_qk_norm函数中的张量重塑逻辑:在python/sglang/srt/models/utils.py中,将apply_qk_norm函数中所有q.view(*q.shape[:-1], -1, head_dim)k.view(*q.shape[:-1], -1, head_dim)调用恢复为q.reshape(-1, head_dim)k.reshape(-1, head_dim)。这确保了在PCG捕获模式下,即使输入张量是非连续的,重塑操作也能正确工作,避免步长不匹配。
  2. 移除与PCG inductor编译器相关的条件逻辑:在apply_qk_norm函数中,删除了对get_global_server_args().piecewise_cuda_graph_compiler != "inductor"的检查,因为该检查原本是为了让inductor融合QK归一化,但回滚后不再需要此优化路径。
  3. 回滚apply_fp8_linear函数中的FP8量化路径:在python/sglang/srt/layers/quantization/fp8_utils.py中,移除了为PCG inductor编译器设计的特殊量化路径。该路径原本在piecewise_cuda_graph_compiler == "inductor"且输入尺度为单元素时,使用纯PyTorch操作进行FP8量化以融合RMSNorm和残差加法。回滚后,统一使用scaled_fp8_quant函数,确保量化行为一致。
  4. 清理导入和配置依赖:在两个文件中,移除了对get_global_server_args的导入和fp8_min常量的引用,因为这些在回滚后的代码中不再需要。
文件 模块 状态 重要度
python/sglang/srt/models/utils.py 模型工具 modified 6.44
python/sglang/srt/layers/quantization/fp8_utils.py 量化层 modified 6.48

关键符号

apply_qk_norm apply_fp8_linear

关键源码片段

python/sglang/srt/models/utils.py core-logic

包含核心修复函数 apply_qk_norm,该函数中的张量重塑操作是导致 AMD GPU 内存访问错误的根本原因。

def apply_qk_norm(
    q: torch.Tensor,
    k: torch.Tensor,
    q_norm: RMSNorm,
    k_norm: RMSNorm,
    head_dim: int,
    alt_stream: Optional[torch.cuda.Stream] = None,
    allow_inplace: bool = True,
) -> Tuple[torch.Tensor, torch.Tensor]:
    """
    应用查询和键的归一化。
    在PCG捕获模式下,q和k可能来自QKV分割的非连续张量,
    使用reshape(-1, head_dim)确保内存布局安全,避免view导致的步长不匹配。
    """
    batch_size = q.size(0)
    q_eps = q_norm.variance_epsilon
    k_eps = k_norm.variance_epsilon
    if (
        _is_cuda # 仅在 CUDA 后端测试过
        and allow_inplace
        and (q_eps == k_eps)
        and not envs.SGLANG_ENABLE_DETERMINISTIC_INFERENCE.get()
        and can_use_fused_inplace_qknorm(head_dim, q.dtype) # 移除对 piecewise_cuda_graph_compiler 的检查
    ):
        fused_inplace_qknorm(
            q=q.view(batch_size, -1, head_dim), # 在融合路径中仍使用 view,因为张量是连续的
            k=k.view(batch_size, -1, head_dim),
            q_weight=q_norm.weight,
            k_weight=k_norm.weight,
            head_dim=head_dim,
            eps=q_eps,
        )
        return q, k
​
    if alt_stream is not None and get_is_capture_mode():
        current_stream = get_current_device_stream_fast()
        alt_stream.wait_stream(current_stream)
        q_by_head = q.reshape(-1, head_dim) # 关键修复:使用 reshape 替代 view,确保非连续张量安全
        q_by_head = q_norm(q_by_head)
        with torch.cuda.stream(alt_stream):
            k_by_head = k.reshape(-1, head_dim)
            k_by_head = k_norm(k_by_head)
        current_stream.wait_stream(alt_stream)
    else:
        q_by_head = q.reshape(-1, head_dim)
        q_by_head = q_norm(q_by_head)
        k_by_head = k.reshape(-1, head_dim)
        k_by_head = k_norm(k_by_head)
    q = q_by_head.view(q.shape) # 归一化后恢复原始形状
    k = k_by_head.view(k.shape)
    return q, k
python/sglang/srt/layers/quantization/fp8_utils.py core-logic

移除了为 PCG inductor 编译器设计的 FP8 量化优化路径,该路径在 AMD 平台上可能引发问题,回滚后使用统一的量化函数。

def apply_fp8_linear(
    input: torch.Tensor,
    weight: torch.Tensor,
    weight_scale: torch.Tensor,
    input_scale: Optional[torch.Tensor] = None,
    compressed_tensor_quant: bool = False,
    cutlass_fp8_supported: bool = False,
    pad_output: Optional[bool] = None,
    use_per_token_if_dynamic: bool = True,
) -> torch.Tensor:
    """
    应用FP8线性层。
    回滚后,移除了为inductor编译器设计的优化路径,
    统一使用scaled_fp8_quant函数进行量化,确保跨平台行为一致。
    """
    if pad_output is None:
        pad_output = not cutlass_fp8_supported and not get_bool_env_var(
            "SGLANG_ENABLE_TORCH_COMPILE"
        )
    output_padding = 17 if pad_output else None
    input_2d = input.view(-1, input.shape[-1])
    output_shape = [*input.shape[:-1], weight.shape[1]]
​
    if compressed_tensor_quant:
        num_token_padding = output_padding
        if cutlass_fp8_supported and weight_scale.numel() == weight.shape[1]:
            num_token_padding = None
        # 回滚关键:移除条件逻辑,直接使用 scaled_fp8_quant
        qinput, x_scale = scaled_fp8_quant(
            input_2d,
            input_scale,
            num_token_padding=num_token_padding,
            use_per_token_if_dynamic=use_per_token_if_dynamic,
        )
    else:
        if input_scale is not None:
            assert input_scale.numel() == 1
            qinput, x_scale = static_quant_fp8(
                input_2d, input_scale, repeat_scale=cutlass_fp8_supported
            )
        else:
            if _is_cuda:
                qinput, x_scale = sglang_per_token_quant_fp8(input_2d)
            else:
                if _is_hip and weight_scale.numel() == 1:
                    qinput, x_scale = scaled_fp8_quant(
                        input_2d,
                        input_scale,
                        use_per_token_if_dynamic=use_per_token_if_dynamic,
                    )
    # 后续 GEMM 逻辑保持不变

评论区精华

回滚的必要性与证据 正确性

PR body 中详细论证了回滚原因,包括 bisect 结果、错误指纹(Memory access fault by GPU)和 CI 验证证据,表明 PR #21734 在 AMD 平台上导致不可恢复的 GPU 内存访问错误。

结论:决定回滚以修复 AMD CI 问题,确保跨平台兼容性。 · 已解决

代码变更的自动化审查 style

gemini-code-assist[bot] 指出 PR 移除了与 'inductor' 编译器和全局服务器参数相关的条件逻辑,并简化了张量重塑(使用 .reshape() 替代 .view())。

结论:变更被认可,无进一步反馈。 · 已解决

风险与影响

技术风险

  • 回归风险:回滚可能重新引入PR #21734原本试图解决的性能问题,特别是在PCG inductor路径下,FP8量化和QK归一化的融合优化丢失,可能导致内核启动开销增加。
  • 兼容性风险:回滚确保AMD平台兼容性,但可能影响其他平台(如NVIDIA)上inductor编译器的优化效果。
  • 安全风险:无直接安全风险,但GPU内存访问错误本身可能指示底层内存管理问题,回滚只是规避而非根本解决。
    具体到文件

  • python/sglang/srt/models/utils.pyapply_qk_norm函数恢复为reshape,在非连续张量场景下更安全,但可能牺牲少量性能。

  • python/sglang/srt/layers/quantization/fp8_utils.py:移除inductor专用路径,可能增加FP8线性层的延迟,尤其是在使用静态每张量激活尺度时。

对用户的影响:普通用户可能不会直接感知,但AMD GPU用户将受益于CI测试的稳定性恢复,避免因内存访问错误导致的推理失败。性能上,可能轻微下降,但权衡了正确性。
对系统的影响:修复了AMD CI上多个测试的失败问题,提升了跨平台兼容性。系统行为回滚到PR #21734之前的状态,确保14个模型文件(如qwen3.py、llada2.py等)在AMD平台上正常运行。
对团队的影响:减少了CI维护负担,避免了因随机GPU错误导致的调试时间。但团队需要后续重新评估如何在AMD平台上安全地实施PCG inductor优化。

跨平台兼容性风险 性能回归风险 核心路径变更

关联 Issue

#23039 Revert "perf: optimize PCG inductor path for FP8 models (#21734)"

完整报告

参与讨论