Prhub

#27630 [AMD] Fuse sigmoid + mul attention output gate into single Triton kernel

原始 PR 作者 yichiche 合并时间 2026-06-11 17:05 文件变更 4 提交数 3 评论 11 代码增减 +130 / -4

执行摘要

融合 sigmoid 和乘法为一个 Triton kernel,降低 AMD GPU 延迟

在 AMD MI355 (gfx950) 上,HIP kernel 启动开销约为 4 us 每个 kernel。Qwen3.5 模型中的 AttentionDecoderLayer 使用两个独立的元素级 kernel:gate = torch.sigmoid(gate)attn_output = attn_output * gate,每层总启动开销 8.6 us 而实际计算仅约 4.5 us。融合后每层可节省约 4.1 us,15 层 attention 共计 ~62 us。

本 PR 是一个针对 AMD GPU 的精准性能优化,内核实现简洁清晰,测试配套完整。值得关注的设计要点是:使用 _is_hip 条件编译避免影响 CUDA 路径;Triton kernel 内部提升精度至 float32 的做法。适合作为 Triton kernel 融合模式的入门参考。

讨论亮点
  • CI 注册:Reviewer bingxche 要求为测试注册 AMD CI,作者 yichiche 随即添加 register_amd_ci
  • 条件判断改进:Reviewer sogalin 质疑 _use_aiter 的使用,指出 kernel 与 aiter 无关,建议改用 _is_hip。作者同意并修改,同时移除了 qwen3_next.py 中不再需要的 _use_aiterget_bool_env_var 导入。

实现拆解

  1. 新增 Triton fused kernel (python/sglang/jit_kernel/triton/sigmoid_gate_mul.py):定义 _sigmoid_gate_mul_kernel Triton JIT kernel,在单个 kernel launch 中完成 x * sigmoid(gate),内部将输入提升至 float32 进行 sigmoid 计算以保持精度。
  2. 修改 qwen3_5.py 和 qwen3_next.py 的 self_attention 方法:将原来的两行 gate = torch.sigmoid(gate); attn_output = attn_output * gate 替换为条件判断:如果 _is_hip 为 True,则调用 sigmoid_gate_mul;否则走原始路径。同时为 qwen3_next.py 添加了 is_hip 导入和 _is_hip 全局变量。
  3. 新增单元测试 (python/sglang/jit_kernel/tests/test_sigmoid_gate_mul.py):包括正确性验证(多 shape、dtype)、输入不变性、输出 dtype 一致性和连续性检查。测试同时注册了 CUDA CI 和 AMD CI。
  4. 将 gate 条件从 _use_aiter 改为 _is_hip:最初 PR 使用 _use_aiter 条件,经 review 指出该 kernel 不依赖 aiter 库,改为直接检查 _is_hip,并移除 _use_aiter 相关导入。
文件 模块 状态 重要度
python/sglang/jit_kernel/triton/sigmoid_gate_mul.py Triton 内核 added 7.7
python/sglang/jit_kernel/tests/test_sigmoid_gate_mul.py 测试 added 7.38
python/sglang/srt/models/qwen3_next.py 模型层 modified 6.37
python/sglang/srt/models/qwen3_5.py 模型层 modified 6.32

关键符号

_sigmoid_gate_mul_kernel sigmoid_gate_mul reference_sigmoid_gate_mul test_sigmoid_gate_mul_correctness test_sigmoid_gate_mul_does_not_modify_inputs test_sigmoid_gate_mul_output_dtype test_sigmoid_gate_mul_contiguous_output

关键源码片段

python/sglang/jit_kernel/triton/sigmoid_gate_mul.py core-logic

核心新增文件。定义 Triton fused kernel,将 sigmoid 和乘法合并为单一 kernel launch,是本次性能优化的核心。

import torch
import triton
import triton.language as tl
​
​
@triton.jit
def _sigmoid_gate_mul_kernel(
    x_ptr,
    gate_ptr,
    out_ptr,
    n_elements,
    BLOCK_SIZE: tl.constexpr,
):
    # 设置 program id 和偏移量
    pid = tl.program_id(0)
    offsets = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
    mask = offsets < n_elements
    # 加载 x 和 gate,提升至 float32 保证 sigmoid 精度
    x = tl.load(x_ptr + offsets, mask=mask).to(tl.float32)
    g = tl.load(gate_ptr + offsets, mask=mask).to(tl.float32)
    # 核心计算:x * sigmoid(gate)
    out = x * tl.sigmoid(g)
    # 写回结果到原始 dtype
    tl.store(out_ptr + offsets, out.to(x_ptr.dtype.element_ty), mask=mask)
​
​
def sigmoid_gate_mul(x: torch.Tensor, gate: torch.Tensor) -> torch.Tensor:
    """Compute x * sigmoid(gate) in a single fused kernel."""
    out = torch.empty_like(x)
    n = x.numel()
    grid = lambda meta: (triton.cdiv(n, meta["BLOCK_SIZE"]),)
    _sigmoid_gate_mul_kernel[grid](x, gate, out, n, BLOCK_SIZE=1024)
    return out
python/sglang/jit_kernel/tests/test_sigmoid_gate_mul.py test-coverage

配套测试文件。覆盖多 dtype、多 shape 的正确性测试、输入不变性、输出 dtype 一致性和连续性。同时注册了 CUDA 和 AMD CI,确保回归安全。

import sysimport pytest
import torchfrom sglang.test.ci.ci_register import register_amd_ci, register_cuda_ci# 注册 CI 标签:CUDA 约 4 分钟,AMD 约 4 分钟
register_cuda_ci(est_time=4, suite="base-b-kernel-unit-1-gpu-large")
register_amd_ci(est_time=4, suite="jit-kernel-unit-test-amd")DEVICE = "cuda"
​
​
def reference_sigmoid_gate_mul(x, gate):
    # 参考实现:使用 PyTorch 的 sigmoid 和乘法
    return x * torch.sigmoid(gate)
​
​
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float16, torch.float32])
@pytest.mark.parametrize(
    "shape",
    [
        (1, 4096),
        (4, 4096),
        (8, 4096),
        (32, 8192),
        (1, 128),
        (16, 16384),
    ],
)
def test_sigmoid_gate_mul_correctness(shape, dtype):
    # 核心正确性测试
    from sglang.jit_kernel.triton.sigmoid_gate_mul import sigmoid_gate_mul
​
    torch.manual_seed(42)
    x = torch.randn(shape, dtype=dtype, device=DEVICE)
    gate = torch.randn(shape, dtype=dtype, device=DEVICE)
​
    ref = reference_sigmoid_gate_mul(x, gate)
    out = sigmoid_gate_mul(x, gate)
​
    # bf16 要求更宽松的容忍度
    rtol = 1e-2 if dtype == torch.bfloat16 else 1e-3
    atol = 2e-2 if dtype == torch.bfloat16 else 1e-3
    torch.testing.assert_close(out, ref, rtol=rtol, atol=atol)
​
​
@pytest.mark.parametrize("shape", [(4, 4096), (1, 128)])
def test_sigmoid_gate_mul_does_not_modify_inputs(shape):
    # 验证融合 kernel 不会修改输入张量
    from sglang.jit_kernel.triton.sigmoid_gate_mul import sigmoid_gate_mul
​
    torch.manual_seed(42)
    x = torch.randn(shape, dtype=torch.bfloat16, device=DEVICE)
    gate = torch.randn(shape, dtype=torch.bfloat16, device=DEVICE)
    x_orig = x.clone()
    gate_orig = gate.clone()
​
    sigmoid_gate_mul(x, gate)
​
    torch.testing.assert_close(x, x_orig, rtol=0, atol=0)
    torch.testing.assert_close(gate, gate_orig, rtol=0, atol=0)
​
​
def test_sigmoid_gate_mul_output_dtype():
    # 验证输出 dtype 与输入一致
    from sglang.jit_kernel.triton.sigmoid_gate_mul import sigmoid_gate_mul
​
    for dtype in [torch.bfloat16, torch.float16, torch.float32]:
        x = torch.randn(4, 4096, dtype=dtype, device=DEVICE)
        gate = torch.randn(4, 4096, dtype=dtype, device=DEVICE)
        out = sigmoid_gate_mul(x, gate)
        assert out.dtype == dtype, f"Expected {dtype}, got {out.dtype}"
​
​
def test_sigmoid_gate_mul_contiguous_output():
    # 验证输出是连续的
    from sglang.jit_kernel.triton.sigmoid_gate_mul import sigmoid_gate_mul
​
    x = torch.randn(4, 4096, dtype=torch.bfloat16, device=DEVICE)
    gate = torch.randn(4, 4096, dtype=torch.bfloat16, device=DEVICE)
    out = sigmoid_gate_mul(x, gate)
    assert out.is_contiguous()
​
​
if __name__ == "__main__":
    sys.exit(pytest.main([__file__, "-v"]))

评论区精华

CI 注册要求 测试

Reviewer bingxche 在测试文件中评论要求注册 AMD CI。初始提交只有 CUDA CI 注册。

结论:作者 yichiche 添加了 `register_amd_ci`,并重写了测试文件,最终版本包含两种 CI 注册。 · 已解决

条件判断改进:_use_aiter 改为 _is_hip 设计

Reviewer sogalin 在 qwen3_5.py 和 qwen3_next.py 的 review 中询问是否使用 _is_hip 替代 _use_aiter,因为该融合 kernel 不依赖 aiter 库。

结论:作者同意并修改为 `if _is_hip`,同时移除了 qwen3_next.py 中不再需要的 `_use_aiter` 和 `get_bool_env_var` 导入。 · 已解决

风险与影响

  • 数值精度:Triton kernel 内部将 x 和 gate 提升至 float32 执行 sigmoid,再写回原始 dtype。与逐点操作的 PyTorch 路径可能存在微小差异,测试中已设置合理的 rtol/atol 容忍度。
  • CUDA 路径保持不变:融合 kernel 仅在 _is_hip 为 True 时启用,CUDA 用户不受影响,风险低。
  • 缺少梯度测试:当前测试仅验证前向正确性,未测试反向传播。若该 kernel 用于训练场景,需额外验证梯度。
  • 维护成本:新增一个小型 Triton kernel,需要配合后续可能的架构变化。
  • AMD GPU 用户:在启用了融合 kernel 的模型(Qwen3.5、Qwen3Next)上,每个 attention 层的 kernel 启动次数从 2 次减少为 1 次,每层节省 ~4.1 us。E2E 吞吐测试显示 +7.8% 总吞吐提升(但可能属于噪声)。
  • 系统影响:无配置变化,无 API 变动,对非 AMD 平台透明。
  • 团队影响:提供了一种减少 kernel launch 开销的可复用模式,其他类似的 gate 融合(如 afmoe.py、bailing_moe_linear.py)可在后续 PR 中采用。
数值精度差异 仅 HIP 启用 缺少梯度测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论