Prhub

#37156 [Diffusion] Fuse Qwen-Image FP8 norm and activation quantization

原始 PR 作者 BBuf 合并时间 2026-08-31 18:19 文件变更 7 提交数 4 评论 0 代码增减 +761 / -17

执行摘要

Qwen-Image FP8 norm 激活量化融合,GB300 端到端提速约 21%

该 PR 的动机来自 Baseten 的 Agentic Kernels in Production 一文所描述的“agentic kernels”融合思路:把扩散模型 denoise 循环中相邻的逐元素算子(norm、AdaLN、静态量化)合并进单个 CUDA kernel,减少 kernel launch 与中间张量读写。Qwen-Image 是 Blackwell 上最高频的 diffusion 模型之一,其注意力与 MLP 入口的 FP8 激活量化(ModelOpt 官方 checkpoint)原本需要先完成 norm + modulation,再单独执行 static_quant_fp8 量化,本 PR 将这两步合而为一,并以 bit-exact 为前提——生成模型对数值扰动敏感,任何 ULP 差异都可能改变输出图像。PR body 用 5+5 平衡进程协议、PNG SHA256 与 SSIM/PSNR/LPIPS 证明了像素级一致。

值得精读。该 PR 是“窄快路径 + 位精确 + 逐项门控”融合工程的高质量样例:1)用 PTX 级对齐解决量化器的 ULP 差异,比“近似一致”的常规做法更严谨,且把两个真实 checkpoint 中点敏感 scale 放进回归矩阵;2)双输出(BF16 + FP8)同时满足性能与生命周期语义,避免了激活内存行为改变带来的隐性问题;3)configure_fp8_norm_quant 的门控集中在 post_load_weights,所有不支持场景静默回退,工程上非常稳健。建议后续融合类 PR 复用这套门控模板与 5+5 平衡基准协议。关注点:无 review 讨论、AMD ROCm CI 失败未解释,合并前建议补一条说明。

讨论亮点

本 PR 没有任何 review 评论或讨论线程(comments_count 与 review_comments_count 均为 0),由作者 BBuf 直接合并,关键设计取舍以 PR body 形式沉淀。其中最重要的数值精确性决策为:Triton 参考静态量化器把 1.0 / scale 下译为 PTX div.full.f32,普通 CUDA 求倒数会差 1 个 FP32 ULP,对真实 checkpoint scale(如 0.4754464328)可能把数值推过 E4M3 中点而改变量化字节,因此融合 kernel 内联同一条 PTX 指令,并把两个 midpoint-sensitive scale 纳入回归矩阵,BF16 输出与 FP8 字节都做完全相等校验。此外,快路径范围被刻意收窄(Blackwell+、BF16 batch-1 连续张量、hidden 3072、affine-free LayerNorm、有限正标量 FP32 input_scale、无 indexed modulation、无 zero-condition-timestep 路径、不可断 CUDA graph),每个不支持场景都保留旧路径。基准协议也值得记录:5 个独立进程、交替顺序 main/PR、GPU 锁频 1800 MHz,用 PNG SHA256 与 SSIM/PSNR/LPIPS 验证像素级一致。

实现拆解

  1. CUDA kernel 层扩展(python/sglang/kernels/jit/csrc/diffusion/norm_scale_shift.cuh):为现有 norm_scale_shift_kernel 增加模板参数 kQuantizeFp8,在逐元素 norm + scale/shift 之后追加 E4M3 静态量化分支;NormScaleShiftParams 新增 quantized 与 input_scale 字段,并新增 NormScaleShiftFp8Kernel、ScaleResidualNormScaleShiftFp8Kernel 两个入口 struct。量化倒数使用内联 PTX div.full.f32(triton_scale_reciprocal),与 Triton 参考量化器逐字节对齐。
  2. JIT 封装与守卫(python/sglang/kernels/ops/diffusion/norm/norm_scale_shift_jit.py):新增 fused_norm_scale_shift_fp8、fused_scale_residual_norm_scale_shift_fp8(返回 normalized + quantized [+ residual_out]),以及对应的 try_* 守卫版本;守卫逐项校验 norm 类型为 layer、无 affine 权重、输入为连续 BF16、Blackwell 及以上设备、_fp8_input_scale 契约(单元素 FP32 CUDA 张量),任一不满足返回 None 走旧路径。norm_scale_shift_module() 在非 Blackwell 设备上显式抛 RuntimeError,把硬件依赖显式化。
  3. 模型主路径集成(python/sglang/multimodal_gen/runtime/models/dits/qwen_image.py):QwenImageTransformerBlock.init 增加 _fp8_img_attn_norm_quant 等 4 个门控标志;新增 _valid_modelopt_fp8_linear(要求 quant_method 为 ModelOptFp8LinearMethod、input_scale 为有限正的单元素 FP32 CUDA 张量)与 _shared_modelopt_fp8_scale(分离 Q/K/V 的 input_scale 必须完全一致);configure_fp8_norm_quant() 在 post_load_weights 阶段按 fused/分离 QKV、added QKV、MLP 入口投影分别开启门控。forward 中新增 _try_fp8_norm_quant 与 _try_fp8_residual_norm_quant:命中融合路径时 FP8 张量作为 attention/MLP 输入,BF16 输出在对应投影完成后 del,维持原激活分配/生命周期语义;未命中则走原 _modulate 路径。
  4. 导出注册与基准:python/sglang/kernels/ops/diffusion/init.py 注册 4 个新符号(映射到 norm.norm_scale_shift_jit);新增 bench_qwen_image_norm_fp8_quant.py kernel benchmark,对比 split(norm + 独立 static_quant_fp8)与 fused 两种实现,并在非 Blackwell 上 marker.skip(该 guard 是 PR 内第二个 commit 补上的修复)。
  5. 测试配套:新增 test_qwen_image_norm_fp8_quant.py(kernel bit-exact 测试,参数化 rows ∈ {1, 127, 1024}、5 个 scale 值,含两个真实 checkpoint 中点敏感 scale,同时校验 BF16 输出与 FP8 字节完全相等);新增 test_qwen_image_fp8_norm_quant.py(模型门控单测,覆盖分离 QKV scale 一致/不一致、fused QKV、非正 scale 禁用融合三种场景)。
文件 模块 状态 重要度
python/sglang/multimodal_gen/runtime/models/dits/qwen_image.py 模型实现 modified 8.86
python/sglang/kernels/jit/csrc/diffusion/norm_scale_shift.cuh CUDA 内核 modified 5.84
python/sglang/kernels/ops/diffusion/norm/norm_scale_shift_jit.py JIT 封装 modified 6.66
test/registered/kernels/ops/diffusion/test_qwen_image_norm_fp8_quant.py 内核测试 added 6.48
test/registered/unit/models/test_qwen_image_fp8_norm_quant.py 门控测试 added 7.09
test/registered/kernels/benchmark/diffusion/bench_qwen_image_norm_fp8_quant.py 基准测试 added 5.92
python/sglang/kernels/ops/diffusion/__init__.py 内核导出 modified 2.73

关键符号

configure_fp8_norm_quant _valid_modelopt_fp8_linear _shared_modelopt_fp8_scale _try_fp8_norm_quant _try_fp8_residual_norm_quant post_load_weights fused_norm_scale_shift_fp8 fused_scale_residual_norm_scale_shift_fp8 try_fused_norm_scale_shift_fp8 try_fused_scale_residual_norm_scale_shift_fp8 triton_scale_reciprocal NormScaleShiftFp8Kernel ScaleResidualNormScaleShiftFp8Kernel

关键源码片段

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

融合路径的模型侧主入口:新增 4 个 FP8 门控标志、_valid_modelopt_fp8_linear/_shared_modelopt_fp8_scale 校验、configure_fp8_norm_quant 与 _try_fp8_norm_quant/_try_fp8_residual_norm_quant,并在 forward 中把 FP8 张量接入 attention/MLP,BF16 输出延迟释放保持生命周期语义。

def configure_fp8_norm_quant(self) -> None:
    """在 checkpoint 的 input_scale 物化后,按条件开启 norm+quant 融合路径。"""
    if not torch.cuda.is_available():
        return
    capability = torch.cuda.get_device_capability()
    # 快路径刻意收窄:仅 3072 隐藏维度、Blackwell 及以上(sm_100+)、
    # 且不启用 zero_cond_t 时间步分支时才可能开启融合。
    if self.dim != 3072 or capability[0] < 10 or self.zero_cond_t:
        return
    if self.attn.use_fused_qkv:
        # 打包 QKV(来自 #37123)场景:直接使用物化后的单一 input_scale。
        self._fp8_img_attn_norm_quant = self._valid_modelopt_fp8_linear(
            self.attn.to_qkv
        )
    else:
        # 分离 Q/K/V 场景:三路投影的 input_scale 必须完全一致,
        # 否则同一个量化结果无法同时满足三路 FP8 消费方,融合保持关闭。
        self._fp8_img_attn_norm_quant = self._shared_modelopt_fp8_scale(
            [self.attn.to_q, self.attn.to_k, self.attn.to_v]
        )
    if self.attn.added_kv_proj_dim is not None:
        # 文本流走 added QKV,门控逻辑与图像流对称。
        if self.attn.use_fused_added_qkv:
            self._fp8_txt_attn_norm_quant = self._valid_modelopt_fp8_linear(
                self.attn.to_added_qkv
            )
        else:
            self._fp8_txt_attn_norm_quant = self._shared_modelopt_fp8_scale(
                [self.attn.add_q_proj, self.attn.add_k_proj, self.attn.add_v_proj]
            )
    if isinstance(self.img_mlp, QwenImageFeedForward):
        # MLP 入口投影同样要求是合法的 ModelOpt FP8 线性层。
        self._fp8_img_mlp_norm_quant = self._valid_modelopt_fp8_linear(
            self.img_mlp.net[0].proj
        )
    if isinstance(self.txt_mlp, QwenImageFeedForward):
        self._fp8_txt_mlp_norm_quant = self._valid_modelopt_fp8_linear(
            self.txt_mlp.net[0].proj
        )
python/sglang/kernels/jit/csrc/diffusion/norm_scale_shift.cuh core-logic

CUDA kernel 本体:新增 kQuantizeFp8 模板分支、triton_scale_reciprocal(PTX div.full.f32 对齐 Triton 量化器)以及 NormScaleShiftFp8Kernel / ScaleResidualNormScaleShiftFp8Kernel 两个入口,是本 PR 性能收益的物理来源。

// Triton 的静态 FP8 量化器把 1.0 / scale 下译为 div.full.f32;
// 普通 CUDA 求倒数相差 1 个 FP32 ULP,在 E4M3 中点附近会改变量化字节,
// 因此这里必须使用同一条 PTX 指令以保证 bit-exact。
SGL_DEVICE float triton_scale_reciprocal(float scale) {
    float reciprocal;
    asm("div.full.f32 %0, %1, %2;" : "=f"(reciprocal) : "f"(1.0f), "f"(scale));
    return reciprocal;
}// kQuantizeFp8 分支:先得到 BF16 的 norm+scale/shift 结果,
// 再乘 input_scale 倒数并 clamp 到 E4M3 表示范围,写出 FP8 张量。
// BF16 与 FP8 双输出同时保留:FP8 直接供 ModelOpt 投影消费,
// BF16 继续存活以维持原有激活分配 / 生命周期语义。
const float input_scale_inv = triton_scale_reciprocal(
    *static_cast<const float*>(params.input_scale));
#pragma unroll
for (int i = 0; i < kVecElems; ++i) {
    const float norm = static_cast<float>(static_cast<bf16_t>((v[i] - mean) * factor));
    const bf16_t rounded = static_cast<bf16_t>(
        norm * (1.0f + static_cast<float>(scv[i])) + static_cast<float>(shv[i]));
    yv[i] = rounded;
    if constexpr (kQuantizeFp8) {
        const float scaled = static_cast<float>(rounded) * input_scale_inv;
        const float clamped = math::min(
            math::max(scaled, -DTypeTrait<fp8_e4m3_t>::kFloatMax),
            DTypeTrait<fp8_e4m3_t>::kFloatMax);
        qv[i] = static_cast<fp8_e4m3_t>(clamped);
    }
}
python/sglang/kernels/ops/diffusion/norm/norm_scale_shift_jit.py core-logic

JIT 封装与守卫层:定义 fused/try_ 两套 FP8 接口,集中表达快路径的全部前置条件(硬件、dtype、连续性、input_scale 契约),是融合安全性的第一道闸门。

def fused_norm_scale_shift_fp8(x, scale, shift, input_scale, eps):
    """单次 kernel 同时产出 BF16 modulation 输出与静态 E4M3 量化结果。"""
    normalized = torch.empty_like(x)
    quantized = torch.empty_like(x, dtype=torch.float8_e4m3fn)
    _module().nss_fp8_row(
        normalized.view(-1, _HIDDEN),
        quantized.view(-1, _HIDDEN),
        x.view(-1, _HIDDEN),
        scale,
        shift,
        input_scale.reshape(1),
        float(eps),
    )
    return normalized, quantized
​
​
def try_fused_norm_scale_shift_fp8(x, weight, bias, scale, shift, input_scale, norm_type, eps):
    # 门控守卫:仅 LayerNorm、无 affine 权重、输入为连续 BF16、运行在
    # Blackwell 及以上、input_scale 为单元素 FP32 CUDA 张量时才融合;
    # 任何一个条件不满足都返回 None,由调用方走原有路径。
    if norm_type != "layer" or weight is not None or bias is not None:
        return None
    if not _nss_activation(x) or not _blackwell_or_newer(x.device):
        return None
    scale = _row_bf16(scale, x.device)
    shift = _row_bf16(shift, x.device)
    if scale is None or shift is None:
        return None
    if not _fp8_input_scale(input_scale, x.device):
        return None
    return fused_norm_scale_shift_fp8(x, scale, shift, input_scale, eps)

评论区精华

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

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

风险与影响

  1. BF16 生命周期依赖 del 顺序:forward 在 joint attention 之后立即 del img_modulated_bf16、txt_modulated_bf16,当前代码不再引用这两个张量,但任何后续改动若在 attention 之后读取 BF16 modulation 输出(调试钩子、新融合、梯度检查点),会触发使用已释放张量或静默语义变化。
  2. 静默回退风险:configure_fp8_norm_quant 依赖 checkpoint 加载后 input_scale 已物化且带 ModelOptFp8LinearMethod;若未来加载时序变化(lazy loading、offload、transformer 替换检测逻辑调整),所有门控会静默保持 False 退化为旧路径——功能安全,但性能回归不易察觉,需要 benchmark 基线监控。
  3. 数值一致性依赖:bit-exact 依赖 Triton 参考量化器仍把倒数下译为 div.full.f32;若 static_quant_fp8 或 ModelOpt 投影实现变化(依赖升级、切换 quant 后端),字节一致性可能被破坏。测试矩阵覆盖了真实 checkpoint scale,但未覆盖 FP16 input_scale 或非 ModelOpt 量化后端。
  4. 硬件范围与 CI:融合仅限 CUDA Blackwell+,norm_scale_shift_module() 在非 Blackwell 上抛 RuntimeError,依赖 try_* 守卫保证不被误调;CPU/XPU/ROCm 环境不受影响,但 AMD ROCm 7.2 CI 失败未在 PR 中说明(PR 内容与 AMD 无关,仍应关注是否为环境问题)。
  5. 性能反模式边界:fused kernel 同时写 BF16 与 FP8 双输出,比纯 FP8 路径多一趟 BF16 写回;microbenchmark 显示 1.1~1.2x 融合收益,但该收益依赖 hidden 3072 且行数在 128~4096 范围,更大 batch 或其它模型尺寸不适用(kHidden 与 _HIDDEN 为硬编码约束)。

用户侧:Qwen-Image + ModelOpt FP8 官方 checkpoint 用户在 GB300(以及 B200)上获得约 21% 端到端提速(denoise/step 均值 306.1 → 240.9 ms),图像输出与 main 像素级一致;非 Blackwell、非 FP8、非 Qwen-Image 用户完全无感(门控关闭走旧路径)。系统侧:diffusion kernels 新增 2 个 CUDA kernel 入口、4 个导出符号,JIT 模块新增 2 个 kernel 运行符,符号注册表同步扩展;kernel 单元测试注册到 4-gpu-b200 网格。团队侧:与 #37123(fused FP8 QKV)叠加后 denoise/step 进一步降至约 201 ms(再降 18%),形成 Qwen-Image Blackwell FP8 融合系列;同仓库近期的 Qwen-Image adaLN 融合(#37144)、bias 吸收(#37116)共用 qwen_image.py 与 norm_scale_shift.cuh,文件冲突面在扩大,需要更细的模块划分。

快路径门控复杂 Blackwell 专属硬件路径 PTX 级数值一致性依赖 AMD ROCm CI 失败未说明 BF16 生命周期依赖 del 顺序

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论