Prhub

#37144 [Diffusion] Fuse Qwen-Image final adaptive LayerNorm

原始 PR 作者 BBuf 合并时间 2026-08-31 18:22 文件变更 3 提交数 3 评论 0 代码增减 +195 / -2

执行摘要

融合 Qwen-Image 最终 adaLN,bit-exact 自校验回退

PR body 明确引用了 [Agentic Kernels in Production] 中描述的 Qwen-Image norm_out 优化:将 norm(hidden_states) * (1 + scale)[:, None, :] + shift[:, None, :] 的多个 eager 操作替换为一个融合 kernel,目标是降低每 denoise step 的 GPU kernel launch 次数,同时以 bit-exact 保证不改变输出。

值得精读。虽然端到端收益为 parity,但 _qwen_norm_out 展示了如何在生产推理引擎中安全地启用 bit-exact kernel 融合:以签名驱动验证、异常和未验证签名自动回退、compile 与 CUDA graph 场景隔离。对于想在 sglang 中做类似融合的开发者,这是很好的参考模板。建议关注后续是否将同类 gate 应用到更多 diT 模型的 final norm。

讨论亮点

该 PR 没有任何 review 评论与讨论线程(author: BBuf 自行合入)。从实现本身可以提炼几个设计权衡:

  • compile 路径保留原表达式:避免 torch.compile 追踪时进入 kernel 分支,测试 test_qwen_norm_out_preserves_compile_path 强制断言不 dispatch kernel。
  • CUDA graph 捕获期禁止首次验证:避免在 capture 中引入同步或触发编译,未验证的签名直接走 eager(test_qwen_norm_out_does_not_verify_during_graph_capture)。
  • 严格 torch.equal 而非容差:测试文件 docstring 明确说明,默认开启的 bit-exact 路径若用容差,真实回归会被 gate 静默吞掉。

实现拆解

  1. python/sglang/multimodal_gen/runtime/models/dits/qwen_image.py 中从 sglang.kernels.ops.diffusion 导入 BitExactFusionGatecan_use_fused_layernorm_modulatefused_layernorm_modulate_rawis_plain_layer_norm;模块级定义 _QWEN_NORM_OUT gate(per_signature=True)并新增 _qwen_norm_out 函数。
  2. _qwen_norm_out 的处理顺序:torch.compiler.is_compiling() 时保留 diffusers 原始表达式;否则先计算调制向量并拆出 scaleshift;当 gate 关闭、不是 plain LayerNorm、或平台/形状不支持时回退 eager;随后构造签名并检查 verified 集合,CUDA graph 捕获期如果签名未验证也直接回退 eager;launch 内核并捕获异常;对已验证签名直接返回 fused 结果,未验证签名则用 torch.equal 与 eager 参考比对,通过后写入 verified 集合。
  3. QwenImageTransformer2DModel.forward 中将 hidden_states = self.norm_out(hidden_states, temb_txt) 改为 hidden_states = _qwen_norm_out(self.norm_out, hidden_states, temb_txt),这是唯一的调用点替换。
  4. 测试配套:test/registered/kernels/ops/diffusion/test_model_fast_paths.py 新增三个测试,分别验证融合结果与 AdaLayerNormContinuous 参考逐位一致、compile 路径不触发 kernel、CUDA graph 捕获期不验证新签名。
  5. 基准配套:test/registered/kernels/benchmark/diffusion/bench_qwen_image_modulation.py 新增 bench_norm_out,覆盖 128/512/2048/4096/4608 token 的 norm_out 场景,并限制在非 ROCm 环境运行(PTX 内核不支持 AMD)。
文件 模块 状态 重要度
python/sglang/multimodal_gen/runtime/models/dits/qwen_image.py 模型实现 modified 7.46
test/registered/kernels/ops/diffusion/test_model_fast_paths.py 快速路径 modified 6.82
test/registered/kernels/benchmark/diffusion/bench_qwen_image_modulation.py 性能基准 modified 5.82

关键符号

_qwen_norm_out test_qwen_norm_out_matches_adaln_reference test_qwen_norm_out_preserves_compile_path test_qwen_norm_out_does_not_verify_during_graph_capture bench_norm_out

关键源码片段

python/sglang/multimodal_gen/runtime/models/dits/qwen_image.py core-logic

核心实现文件,新增 `_qwen_norm_out` 融合函数并将 forward 中的 `self.norm_out` 调用替换为融合入口,是整个 PR 的源码载体。

def _qwen_norm_out(
    norm_out: AdaLayerNormContinuous,
    hidden_states: torch.Tensor,
    conditioning_embedding: torch.Tensor,
) -> torch.Tensor:
    # torch.compile 场景保留 diffusers 原始表达式,避免 trace 时进入 kernel 分支
    if torch.compiler.is_compiling():
        return norm_out(hidden_states, conditioning_embedding)
​
    # 先算调制向量并拆成 scale、shift;这里直接用子模块,绕开自定义算子包装的调度开销
    emb = norm_out.linear(norm_out.silu(conditioning_embedding).to(hidden_states.dtype))
    scale, shift = torch.chunk(emb, 2, dim=1)
​
    # 任一前置条件不满足(gate 关闭、非 plain LayerNorm、平台 / 形状不支持)都回退 eager
    if (
        _QWEN_NORM_OUT.disabled
        or not is_plain_layer_norm(norm_out.norm, hidden_states.shape[-1])
        or not can_use_fused_layernorm_modulate(hidden_states, scale, shift)
    ):
        return (
            norm_out.norm(hidden_states) * (1 + scale)[:, None, :] + shift[:, None, :]
        )
​
    # 用 dtype/device/stride/eps 组成签名,匹配已 verified 的集合
    sig = (
        hidden_states.dtype,
        hidden_states.device,
        hidden_states.shape[0],
        hidden_states.shape[-1],
        hidden_states.stride(-1),
        scale.stride(0) if scale.shape[0] > 1 else hidden_states.shape[-1],
        shift.stride(0) if shift.shape[0] > 1 else hidden_states.shape[-1],
        norm_out.norm.eps,
    )
    verified = sig in _QWEN_NORM_OUT_SIGS
​
    # CUDA graph 捕获期禁止做首次签名验证,避免在 capture 中触发同步或编译
    if not verified and torch.cuda.is_current_stream_capturing():
        return (
            norm_out.norm(hidden_states) * (1 + scale)[:, None, :] + shift[:, None, :]
        )
​
    try:
        fused = fused_layernorm_modulate_raw(
            hidden_states, scale, shift, norm_out.norm.eps
        )
    except Exception as exc:
        # 内核 launch 异常时关闭 gate 并回退 eager,防止平台问题导致推理失败
        _QWEN_NORM_OUT.on_exception(exc, logger=logger)
        return (
            norm_out.norm(hidden_states) * (1 + scale)[:, None, :] + shift[:, None, :]
        )
​
    if verified:
        return fused
​
    # 新签名:与 eager 参考逐位比对,一致则写入 verified 集合,否则回退
    reference = (
        norm_out.norm(hidden_states) * (1 + scale)[:, None, :] + shift[:, None, :]
    )
    return _QWEN_NORM_OUT.accept_or_fallback(
        fused,
        reference,
        sig=sig,
        logger=logger,
        mismatch_msg=(
            'Qwen-Image fused norm_out is not bit-exact on this platform; '
            'falling back to eager'
        ),
    )
test/registered/kernels/ops/diffusion/test_model_fast_paths.py test-coverage

新增三个核心单测,覆盖 bit-exact 参考一致性、compile 路径隔离和 CUDA graph 捕获期行为,是保障融合安全性的关键测试。

def test_qwen_norm_out_does_not_verify_during_graph_capture(monkeypatch):
    # 重置 gate 状态,确保捕获期间「未验证」时不会误用融合 kernel
    qwen_image._QWEN_NORM_OUT.disabled = False
    qwen_image._QWEN_NORM_OUT.verified = False
    qwen_image._QWEN_NORM_OUT_SIGS.clear()
    norm_out = (
        qwen_image.AdaLayerNormContinuous(3072, 3072, elementwise_affine=False, eps=1e-6)
        .cuda()
        .bfloat16()
    )
    hidden_states = torch.randn(1, 17, 3072, device='cuda', dtype=torch.bfloat16)
    conditioning = torch.randn(1, 3072, device='cuda', dtype=torch.bfloat16)
    expected = norm_out(hidden_states, conditioning)
​
    # 模拟 CUDA graph 捕获:任何内核 dispatch 都会让测试失败
    monkeypatch.setattr(torch.cuda, 'is_current_stream_capturing', lambda: True)
    monkeypatch.setattr(
        qwen_image,
        'fused_layernorm_modulate_raw',
        lambda *args, **kwargs: pytest.fail('capture must not verify a new layout'),
    )
​
    assert torch.equal(_qwen_norm_out(norm_out, hidden_states, conditioning), expected)
    # 捕获期间不允许新增任何签名
    assert not qwen_image._QWEN_NORM_OUT_SIGS

评论区精华

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

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

风险与影响

  1. 平台兼容性:fused_layernorm_modulate_raw 依赖 NVIDIA inline PTX,ROCm 上无法使用;测试里 requires_inline_ptx 跳过 AMD,bench 也跳过 norm_out 场景,其余 AMD 场景仅验证 eager 回退。
  2. CUDA graph 稳定性:捕获期间未验证的签名回退 eager,理论上不会破坏捕获,但若未来新增 shape/stride 组合未在捕获前验证,融合将长期不生效,属于隐式性能退化而非功能错误。
  3. 端到端收益不明确:PR body 实测 end-to-end 为 -0.392% 的微小回归(在 4.7-5.7 ms/step 标准差内),说明该优化对整体吞吐提升有限,若其他配置(非 B300、非 3072 hidden)收益更小甚至为负。
  4. 异常兜底:launch 异常时 on_exception 会关闭 gate 并回退,但异常路径本身可能掩盖 PTX 变更导致的兼容性问题,需要持续关注 kernel 报错日志。

影响范围集中在 Qwen-Image 推理的最后一个 norm 环节,仅改动 qwen_image.py 中的一处调用点。对用户而言,生成图像像素与原先完全一致(PR 中 10/10 输出 SHA256 相同),性能上 kernel 级 latency 降低但端到端基本持平,主要收益是减少每步 GPU kernel launch 次数(每 denoise step 减 6 个 launch)。对团队而言,该 PR 提供了一个 BitExactFusionGate 应用的完整范例(签名验证 + 自动回退 + 捕获保护),可复用于后续其他模型的 norm/modulate 融合。

依赖 NVIDIA PTX CUDA graph 捕获期限制 端到端收益为 parity ROCm 自动回退 测试仅覆盖 CUDA

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论