[None][fix] visual_gen FLUX: enable fused DiT QK-norm + RoPE by default#15676
[None][fix] visual_gen FLUX: enable fused DiT QK-norm + RoPE by default#15676chang-l wants to merge 3 commits into
Conversation
📝 WalkthroughWalkthrough
ChangesFlux fused QK-norm + RoPE
Estimated code review effort🎯 2 (Simple) | ⏱️ ~5 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
The fused DiT QK-norm + RoPE kernel was added for FLUX in NVIDIA#11869 and was enabled by default there via an auto-default (fused on for per-head norm). NVIDIA#13052 (WAN cross-head) later changed that base default to opt-in (... if ... is not None else False), wired WAN's own opt-in, but did not migrate FLUX -- which had relied on the auto-default. FLUX has run the unfused F.rms_norm + apply_rotary_emb path ever since (main, rc18, rc19), contradicting the FluxJointAttention docstring. Make FLUX opt in explicitly in FluxJointAttention (propagates to Flux2ParallelSelfAttention, covering FLUX.1/FLUX.2 dual- and single-stream blocks). The fused op currently requires TP=1 (apply_packed_qk_norm_rope asserts tp_size == 1), so gate the opt-in on tp_size == 1 and fall back to the unfused path under tensor parallelism, mirroring WAN. Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
21f5e08 to
0df87f4
Compare
Signed-off-by: Chang Liu <9713593+chang-l@users.noreply.github.com>
|
/bot run |
|
PR_Github #57037 [ run ] triggered by Bot. Commit: |
|
PR_Github #57037 [ run ] completed with state
|
|
/bot run --disable-fail-fast |
2 similar comments
|
/bot run --disable-fail-fast |
|
/bot run --disable-fail-fast |
|
PR_Github #57048 [ run ] triggered by Bot. Commit: |
|
/bot run |
|
PR_Github #57048 [ run ] completed with state
|
|
PR_Github #57099 [ run ] triggered by Bot. Commit: |
|
PR_Github #57099 [ run ] completed with state
|
|
/bot run |
|
PR_Github #57147 [ run ] triggered by Bot. Commit: |
|
PR_Github #57147 [ run ] completed with state
|
|
/bot run |
|
PR_Github #57175 [ run ] triggered by Bot. Commit: |
|
PR_Github #57175 [ run ] completed with state
|
|
/bot run |
|
PR_Github #57226 [ run ] triggered by Bot. Commit: |
|
PR_Github #57226 [ run ] completed with state
|
|
/bot run |
|
/bot run |
2 similar comments
|
/bot run |
|
/bot run |
|
PR_Github #57282 [ run ] triggered by Bot. Commit: |
|
PR_Github #57282 [ run ] completed with state
|
|
/bot run |
|
PR_Github #57294 [ run ] triggered by Bot. Commit: |
|
PR_Github #57294 [ run ] completed with state
|
|
/bot run |
|
PR_Github #57523 [ run ] triggered by Bot. Commit: |
|
PR_Github #57523 [ run ] completed with state
|
Description
The fused DiT QK-norm + RoPE kernel was added for FLUX in #11869 and was enabled by default there: the base
Attentiondefault auto-enabled it for per-head norm, and FLUX (qk_norm_mode="per_head") rode that auto-default.#13052 ("Fused cross-head QK Norm + RoPE kernel for WAN", merged 2026-05-13) then changed the base default to opt-in:
That PR added WAN's explicit opt-in but did not migrate FLUX, which had been relying on the auto-default. So FLUX silently regressed to the unfused
F.rms_norm+apply_rotary_embpath from #13052 onward (still off inmain; reproduced onrelease:1.3.0rc18/rc19). The staleFluxJointAttentiondocstring ("fused path is the default") is the fossil.Fix
Make FLUX opt in explicitly: pass
fuse_qk_norm_rope=(tp_size == 1)inFluxJointAttention(propagates toFlux2ParallelSelfAttention, covering FLUX.1/FLUX.2 dual- and single-stream blocks).The opt-in is gated on TP=1 because the fused op currently requires TP=1 —
apply_packed_qk_norm_ropeassertstp_size == 1— so under TP>1 FLUX falls back to the unfused path. This mirrors WAN's(tp_size == 1)gating. (FLUX's per-head/intra-head norm is itself TP-invariant; the gate reflects the op's current TP=1 implementation limit, not a math/correctness issue.)Why explicit opt-in rather than restoring a base-class auto-default: kernel applicability depends on RoPE format/convention, not just norm-mode/TP. E.g. Qwen-Image is per-head but stays
Falsebecause it uses complex-valued RoPE (freqs_cis), incompatible with the kernel's real cos/sin format (documented TODO intransformer_qwen_image.py). Only the model author knows all constraints hold, so per-model opt-in is the right design.Verification
self.fuse_qk_norm_roperesolves toTruefor every FLUX attention module when TP=1 (wasFalsesince [#12716][feat] Fused cross-head QK Norm + RoPE kernel for WAN #13052);Falseunder TP>1.release:1.3.0rc19, TP=1): end-to-endgenerate()12.298 s → 11.771 s = 1.045× faster (−4.3% wall time), against a −0.01% null-control noise floor (single-process order-rotated A/B, cold round discarded, n=4). torch.profiler confirmstrtllm::fused_dit_qk_norm_rope(→fusedDiTQKNormRopeKernel<128,true>, 56 modules × 50 steps = 2800 calls) is exercised only with the flag on; the off path runs separate inductor-fused Triton RMSNorm kernels. Output images are visually identical between conditions. (The +4.3% is over a torch-compiled baseline, so it sits a touch below [None][feat] Add fused DiT QK Norm + RoPE CUDA kernel for FLUX #11869's standalone-microbench numbers.)Test
tests/unittest/_torch/visual_gen/test_flux_attention.py::TestFluxAttentionBackend::test_fused_qk_norm_rope_enabled_only_for_tp1tests/unittest/_torch/thop/parallel_hw_agnostic/test_fused_dit_qk_norm_rope.pyPR Checklist