Prhub

#34004 [diffusion] FLUX.1 fused adaLN modulate (bit-exact) + RoPE cache hoist, LN-affine folding behind quality=high (H200 e2e -3.5% lossless / -6.9% high)

原始 PR 作者 BBuf 合并时间 2026-08-08 13:07 文件变更 7 提交数 5 评论 1 代码增减 +671 / -22

执行摘要

FLUX.1 融合调制与 RoPE 缓存,提速 3.5%/6.9%

FLUX.1 在 H200 上已跑到约 97.8% GPU 占用,剩余 eager 逐元素链(占 step kernel 时间 13.4%)的每一毫秒都直接转化为墙钟,PR body 原话:"FLUX.1 runs at ~97.8% GPU busy, so the remaining eager elementwise chains (13.4% of step kernel time) convert directly into wall-clock." 具体账目:adaLN modulate 链 norm(x) * (1 + scale) + shift 每 step 有 114 个站点(19 个 dual block × 4 + 38 个 single block × 1),每个站点三个 eager elementwise kernel,其中 mul 与 add 是对 [1, L, 3072] 激活的两次完整 HBM 遍历(S1 trace 实测 mul 3.09 ms/step + add 3.14 ms/step);RoPE 缓存重建则让每个 attention 都重复执行 torch.cat([cos.float(), sin.float()], -1),57 次启动/step、每图 2,850 次,几乎全部产出同一份 tensor。这是 #33819 之后既定清理路线的第三波,目标是在默认路径输出逐位不变的前提下,把这些浪费直接换成吞吐。

值得精读。核心看三点:一是 bit-exact 融合方法论——逐算子舍入复现 + 显式阻止 FMA 收缩 + 守卫回退 + torch.compile 图内禁静默回退,可推广到任意 elementwise 链融合;二是 quality 分层 + mount/unmount 协议,这是仓库在 #28708 回退教训后沉淀的“近似加速落地”标准答案;三是 PR body 的数值论证格式(HBM pass 数、microbench 归因 vs 端到端实测、md5/PSNR 三件套),是高质量性能 PR 的范本。建议抽读 flux.py 的 _flux_modulate / _flux_norm_modulate 与 modulate_scale_shift.cuh 的 modulate_value。

讨论亮点

PR 无外部 review 评论:唯一的 comment 是作者 BBuf 附的 CI 运行链接,且由作者自行合并(merged_by: BBuf)。有效的“讨论”全部沉淀在 PR body 的决策自述里:一是为何 lossless 路径不融合 LN 归约——“aten's LayerNorm uses per-element Welford updates (mean + delta * (1.f/new_count)), count-weighted cuWelfordCombine merges, and the accurate multi-instruction rsqrtf — with the effective FMA-contraction choices baked into shipped libtorch SASS. Reproducing that bit-for-bit in Triton is a full task with real failure risk, so the LN fusion ships as the gated affine-folding instead (the fallback the plan anticipated)”;二是对 #28708 历史回退教训的回应——当年回退原因是“the fused path can move generated images away from the original model behavior”,而 quality 分层(#33453)让这类近似加速以 opt-in 方式重新落地:“sites default off, lossless requests keep the bit-exact reference path”。

实现拆解

  1. RoPE cos/sin 缓存提升(commit 1,lossless)。在 flux.py 新增 _rope_cos_sin_cache(),把 FluxAttention.forward 中每次调用都执行的 fp32 拼接(torch.cat([cos.float(), sin.float()], -1))上移到 FluxTransformer2DModel.forward:每个去噪 step 只构建一次(SP 文本分片激活时为 single-block 重排单独再构建一份),以 tensor 形式下传给各 attention。FluxAttention 仍兼容接收原始 (cos, sin) 元组的调用方(内部同样走 _rope_cos_sin_cache,已构建的 tensor 直接透传)。每 step 57 次启动降为 1 次,所有 attention 消费的数值与改动前完全相同,属构造性 lossless。

  2. bit-exact 融合 modulate(commit 2,lossless)。内核层新增 python/sglang/kernels/jit/csrc/diffusion/modulate_scale_shift.cuh(+221),与 residual_gate_add.cuh 同为 vec-8 行广播 tile 结构(kVec = 16 / sizeof(T)),用 grid-stride 覆盖 row-tile 超过硬件 gridDim.y(65535)上限的情形;核心 modulate_value 按 round(round(x * round(1 + scale)) + shift) 逐步舍入,复现 eager 链的逐算子 fp32-opmath/舍入到存储精度边界,并结构性阻止 FMA 收缩。Python 封装新增 python/sglang/kernels/ops/diffusion/modulate_scale_shift.py(+95),cache_once 按 dtype 缓存 JIT 模块,register_custom_op 注册 diffusion_modulate_scale_shift 自定义算子(含 _fake_impl 供 torch.compile 做形状推导);can_use_modulate_scale_shift_cuda 守卫半精度 dtype、同设备、3D/2D 形状匹配、连续性、非空、D 为向量宽度倍数与 16 字节对齐。模型接线在 flux.py(+149/-22):新增 _flux_modulate(进程级一次性禁用开关 _FLUX_MODULATE_CUDA_DISABLED + torch.compiler.is_compiling() 图内抛错的逃生口)与本地子类 FluxAdaLayerNormZero / FluxAdaLayerNormZeroSingle(参数与 state-dict 与 diffusers 父类一致),替换 dual-block 的 norm1/norm1_context 与 single-block 的 norm;norm2/norm2_context 的调制也统一改经 _flux_norm_modulate 路由,Nunchaku 分支保持原样。一个值得注意的细节:守卫的 is_contiguous() 检查与 PyTorch 跳过 size-1 维的 contiguous 语义一致,因此 emb.chunk(6) 产生的 [1, D] 视图无需 .contiguous() 拷贝即可进内核(测试 test_modulate_scale_shift_adaln_chunk_views 专门覆盖)。

  3. LN affine 折叠(commit 3,quality="high" 门控)。新增 python/sglang/kernels/ops/diffusion/fused_ln_modulate.py(+85):mark_fused_ln_modulate_site 给站点打默认关闭的属性,mount/unmount_fused_ln_modulate 沿 root.modules() 递归开关,fused_ln_modulate 实现 F.layer_norm(x, weight=(1 + scale).reshape(-1), bias=shift.reshape(-1), eps=eps) 的单 kernel 折叠;can_fuse_ln_modulate 逐调用守卫要求 B == 1(折叠 affine 是 [D] 行)。denoising.py 的 _maybe_toggle_quality_fusions 在 batch 边界按 quality == "high" 挂载并打印 Mounted fused LN+modulate (affine folding) for quality=high。该路径非 bit-exact(bf16 舍入序差异,max abs diff ~0.05)故必须门控;PR body 论证了 lossless 路径不融合 LN 归约的原因——aten LayerNorm 用逐元素 Welford 更新 + count-weighted cuWelfordCombine 合并 + 多指令 rsqrtf,Triton 逐位复刻风险高(#33819 的审计也排除了现有候选)。

  4. 测试与验证配套。新增两个注册 CUDA 单测:test_modulate_scale_shift.py(FLUX 真实形状 [1, L, 3072] + batched + 奇数长度共 5 形状 × bf16/fp16 的 torch.equal 位级契约,注册 1-gpu-large 与 4-gpu-b200 两个 runner)与 test_fused_ln_modulate.py(assert_close(atol=0.0625, rtol=0.05) 接近性契约 + 挂载协议与 B==1 守卫)。端到端:lossless 主路径 32 张图(main / commit-1 / PR / final 各档)md5 全同 cad50fb5...;high 档 PSNR 35.48–37.95 dB、SSIM 0.9785–0.9916;相邻套件 45 passed。

文件 模块 状态 重要度
python/sglang/multimodal_gen/runtime/models/dits/flux.py 模型实现 modified 8.74
python/sglang/kernels/ops/diffusion/modulate_scale_shift.py 内核封装 added 7.13
python/sglang/kernels/jit/csrc/diffusion/modulate_scale_shift.cuh CUDA 内核 added 5.98
python/sglang/kernels/ops/diffusion/fused_ln_modulate.py 挂载协议 added 6.8
python/sglang/multimodal_gen/runtime/pipelines_core/stages/denoising.py 去噪阶段 modified 5.46
test/registered/kernels/ops/diffusion/test_modulate_scale_shift.py 内核测试 added 6.87
test/registered/kernels/ops/diffusion/test_fused_ln_modulate.py 内核测试 added 6.33

关键符号

_flux_modulate _flux_norm_modulate _rope_cos_sin_cache FluxAdaLayerNormZero.forward FluxAdaLayerNormZeroSingle.forward modulate_scale_shift_cuda can_use_modulate_scale_shift_cuda _modulate_scale_shift_custom_op _jit_modulate_scale_shift_module fused_ln_modulate can_fuse_ln_modulate mark_fused_ln_modulate_site mount_fused_ln_modulate unmount_fused_ln_modulate _maybe_toggle_quality_fusions modulate_value modulate_scale_shift_vec_kernel

关键源码片段

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

主战场(+149/-22):新增 _flux_modulate / _flux_norm_modulate 分发、FluxAdaLayerNormZero{,Single} 本地子类替换 diffusers 父类、norm2/norm2_context 调制路由改造与 _rope_cos_sin_cache 缓存提升;freqs_cis 参数契约从仅元组扩展为 tensor| 元组。

# FLUX.1 adaLN modulate 的统一分发入口:
# · 默认(lossless):无 affine LayerNorm + bit-exact 融合 modulate,输出与 eager 逐位一致;
# · quality="high" 且守卫通过:把 modulate 折叠进 LN 的 elementwise affine(单 kernel,非 bit-exact)。
# 挂载状态是模块上的开关属性,由 DenoisingStage 按 batch 边界统一切换,站点默认关闭(参考路径)。_FLUX_MODULATE_CUDA_DISABLED = False
​
​
def _flux_modulate(x, scale, shift):
    # 单 CUDA kernel 完成 x * (1 + scale[:, None]) + shift[:, None]。
    # kernel 复现 eager 链的逐算子 fp32-opmath / 舍入到存储精度(bf16/fp16)边界,
    # 因此 bit-exact(torch.equal 验证),无需 quality 门控;守卫失败回退 eager。
    global _FLUX_MODULATE_CUDA_DISABLED
​
    if not _FLUX_MODULATE_CUDA_DISABLED and can_use_modulate_scale_shift_cuda(
        x, scale, shift
    ):
        try:
            return modulate_scale_shift_cuda(x, scale, shift)
        except Exception as exc:
            # 编译图内禁止静默回退:一旦回退分支被固化进图就永远失去 fast path;
            # 图外首次失败则进程内一次性禁用,避免每步重复告警与重试。
            if torch.compiler.is_compiling():
                raise
            logger.warning_once(f"Disabling FLUX modulate CUDA fast path: {exc}")
            _FLUX_MODULATE_CUDA_DISABLED = True
​
    return x * (1 + scale[:, None]) + shift[:, None]
​
​
def _flux_norm_modulate(site, norm, x, scale, shift):
    # FLUX adaLN 站点的统一入口:norm(x) * (1 + scale) + shift。
    # 默认 = 无 affine 的 LayerNorm + bit-exact 融合 modulate(两趟 HBM);
    # 站点被挂载(quality="high")且逐调用守卫通过时,改为单 kernel 的
    # F.layer_norm(x, weight=1 + scale, bias=shift) 折叠(一趟 HBM,非 bit-exact)。
    # scale/shift 是 emb.chunk 产生的 [1, D] 视图时,PyTorch 的 is_contiguous()
    # 会跳过 size-1 维,因此能直接通过内核守卫,无需 .contiguous() 拷贝。
    if fused_ln_modulate_active(site) and can_fuse_ln_modulate(x, scale, shift):
        return fused_ln_modulate(x, scale, shift, norm.eps)
    return _flux_modulate(norm(x), scale, shift)
​
​
class FluxAdaLayerNormZero(AdaLayerNormZero):
    # diffusers AdaLayerNormZero 的本地子类:参数与 state-dict 与父类完全一致,
    # 仅把 modulate 路由到 _flux_norm_modulate,并在构造时标记为可折叠站点。
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        mark_fused_ln_modulate_site(self) # 默认关闭,quality="high" 时统一挂载
​
    def forward(self, x, timestep=None, class_labels=None, hidden_dtype=None, emb=None):
        if self.emb is not None:
            emb = self.emb(timestep, class_labels, hidden_dtype=hidden_dtype)
        emb = self.linear(self.silu(emb))
        shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = emb.chunk(6, dim=1)
        x = _flux_norm_modulate(self, self.norm, x, scale_msa, shift_msa)
        return x, gate_msa, shift_mlp, scale_mlp, gate_mlp
python/sglang/kernels/ops/diffusion/modulate_scale_shift.py core-logic

新内核的 Python 侧封装(+95):JIT 加载、register_custom_op 注册(含 fake_impl 供 torch.compile 形状推导)与 can_use_modulate_scale_shift_cuda 守卫,是 bit-exact 契约的入口。

# 融合 adaLN modulate 的 Python 侧封装(新文件 +95 行):
# JIT 加载 CUDA 内核 + 守卫校验 + 自定义算子注册三件事。
# 数值契约:内核复现 eager 链的逐算子 fp32-opmath / 舍入到存储类型边界
# (仅 fp16/bf16),输出与 eager 完全 bit-exact,因此不需要 quality 门控。_SUPPORTED_DTYPES = (torch.float16, torch.bfloat16)
_ALIGN_BYTES = 16
​
​
@cache_once
def _jit_modulate_scale_shift_module(dtype: torch.dtype) -> Module:
    args = make_cpp_args(dtype)
    return load_jit(
        "diffusion_modulate_scale_shift",
        *args,
        cuda_files=["diffusion/modulate_scale_shift.cuh"],
        cuda_wrappers=[
            (
                "modulate_scale_shift",
                "sglang_modulate_scale_shift::" f"ModulateScaleShiftKernel<{args}>::run",
            ),
        ],
    )
​
​
def _fake_impl(x, scale, shift):
    # torch.compile 下自定义算子不透明,fake 实现负责形状 / 设备推导
    return torch.empty_like(x)
​
​
@register_custom_op(
    op_name="diffusion_modulate_scale_shift",
    mutates_args=[],
    fake_impl=_fake_impl,
)
def _modulate_scale_shift_custom_op(x, scale, shift):
    out = torch.empty_like(x)
    module = _jit_modulate_scale_shift_module(x.dtype)
    module.modulate_scale_shift(out, x, scale, shift)
    return out
​
​
def _aligned(t):
    return t.data_ptr() % _ALIGN_BYTES == 0
​
​
def can_use_modulate_scale_shift_cuda(x, scale, shift) -> bool:
    # 守卫语义与调用场景严格对应:
    # · 只服务半精度、CUDA、同设备、3D/2D 形状匹配、非空、连续输入;
    # · D 须为向量宽度(16 字节 / 元素大小)整数倍,且三个指针 16 字节对齐;
    # · PyTorch 的 is_contiguous() 会跳过 size-1 维,因此 emb.chunk() 产生的
    # [1, D] 视图可直接进内核而无需 .contiguous() 拷贝。
    if (
        x.dtype not in _SUPPORTED_DTYPES
        or scale.dtype != x.dtype
        or shift.dtype != x.dtype
        or not (x.is_cuda and scale.is_cuda and shift.is_cuda)
        or not (x.device == scale.device == shift.device)
        or x.dim() != 3
        or scale.dim() != 2
        or shift.shape != scale.shape
        or scale.shape != (x.shape[0], x.shape[-1])
        or not (x.is_contiguous() and scale.is_contiguous() and shift.is_contiguous())
        or x.numel() == 0
    ):
        return False
    vec = _ALIGN_BYTES // x.element_size()
    return (
        x.shape[-1] % vec == 0 and _aligned(x) and _aligned(scale) and _aligned(shift)
    )
​
​
def modulate_scale_shift_cuda(x, scale, shift):
    # Fused x * (1 + scale[:, None]) + shift[:, None](与 eager 完全 bit-exact)
    if not can_use_modulate_scale_shift_cuda(x, scale, shift):
        raise RuntimeError("unsupported input for modulate_scale_shift CUDA")
    return _modulate_scale_shift_custom_op(x, scale, shift)

评论区精华

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

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

风险与影响

1)lossless 位级契约现在依赖运行时 JIT 内核与守卫:守卫失败会静默回退 eager(正确性不变),但 _FLUX_MODULATE_CUDA_DISABLED 在首次异常后进程内永久禁用 fast path(性能劣化),且 torch.compiler.is_compiling() 图内异常会直接抛出,CUDA graph 捕获场景需留意。2)挂载/卸载与禁用开关都是进程级可变状态,正确性依赖“quality 参与 dynamic-batch 签名、单 worker batch 内 quality 均匀”的批注不变量;若未来放开混批,存在 wrong-path 执行风险。3)守卫覆盖有限:B > 1、非对齐、非连续的批量请求自动退回 eager(正确但不加速);FLUX.1-dev 的 embedded guidance 场景恰好 B=1 且 gate 稠密,实测全命中。4)SP 文本分片下 single-block 的 RoPE 缓存独立重排没有专门单测,md5 验证主要在单卡 H200 完成。5)high 档数值差异(max abs diff ~0.05)是契约内的,但换模型或换 torch 版本时 PSNR 需重新验证。

对 FLUX.1 系用户:默认(lossless)路径输出逐位不变而端到端快约 3.5%(denoise -3.0%),quality="high" 用户相对 main lossless 总收益 -6.9%(denoise -5.9%),图像质量保持 PSNR 35+ dB。对系统:新增 modulate_scale_shift JIT 内核与 fused_ln_modulate 挂载协议两个可复用构件,GLM-Image 等同族模型可直接套用;CI 增加两个注册 kernel 单测套件。对团队:确立了“lossless 位级一致 + high 档 opt-in 近似”验收模板(microbench 归因 + 端到端 md5 + PSNR/SSIM 三件套),后续同类融合 PR 可照此评审。影响面限于 diffusion 路径与 FLUX.1 模型家族,不触及 SRT 核心推理路径。

lossless 位级契约依赖运行时 JIT 内核 进程级全局状态(禁用开关与挂载标志) 单 worker batch 内 quality 均匀性假设 SP 文本分片 RoPE 重排路径缺独立测试 B>1 批量请求自动退回 eager

关联 Issue

#33451 [diffusion] FLUX.2 VAE decoder fast path behind quality=high (H200: 1024^2 97.6->29.2 ms, 2048^2 437.2->168.5 ms)
#33536 [diffusion] Fuse DiT FFN tanh-GELU into up-proj GEMM (cublasLt epilogue) behind quality=high (Qwen-Image 1024^2 denoise 12.36 -> 12.05 s on H200)
#33819 [diffusion] FLUX.1 bit-exact residual-gate fast path + tanh-GELU epilogue behind quality=high (H200 e2e -1.1% lossless / -4.3% high)

完整报告

参与讨论