Repository navigation
[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) - #34004
Merged
Conversation
BBuf
requested review from
AgainstEntropy,
DarkSharpness,
HaiShaw,
HydraQYH,
celve,
mickqian,
ping1jing2,
yichiche,
yingluosanqian and
yuan-luo
as code owners
August 7, 2026 16:03
BBuf
force-pushed
the
k2-flux-modulate-rope
branch
3 times, most recently
from
August 7, 2026 17:24
a121326 to
bd9b262
Compare
FluxAttention rebuilt the identical fp32 cos_sin_cache tensor (torch.cat of the cos/sin halves) inside every attention call: 57 launches per denoising step for FLUX.1-dev (19 dual + 38 single blocks). Build it once per step in FluxTransformer2DModel.forward (separately for the single-block relayout when SP text sharding is active) and pass the cached tensor down; attention keeps accepting the raw (cos, sin) tuple for other callers. Lossless: every attention consumes the same tensor values as before (verified: full-image md5 unchanged vs main).
The diffusers adaLN modulate chain norm(x) * (1 + scale) + shift ran three
elementwise kernels per site (tiny 1+scale row op, broadcast mul, broadcast
add) at 114 sites per FLUX.1-dev step (19 dual blocks x 4 + 38 single
blocks); the mul+add pair alone is two full HBM passes over the [1, L, 3072]
activation.
Add a CUDA kernel (modulate_scale_shift.cuh, JIT, sibling of
residual_gate_add) computing round(round(x * round(1+scale)) + shift) with
the eager chain per-op fp32-opmath/round-to-storage boundaries reproduced
exactly, so it is bit-exact vs eager (torch.equal; unit-tested on FLUX
shapes for bf16/fp16, batched rows, and adaLN chunk views) and wired
unconditionally: local FluxAdaLayerNormZero{,Single} subclasses and the
dual-block norm2 modulate sites. Guarded inputs fall back to the eager
expression; the nunchaku branch (scale without 1+) is untouched.
For quality=high requests, replace the affine-free LayerNorm + fused modulate pair with a single aten layer_norm call whose elementwise affine carries the modulation row: layer_norm(x, weight=1+scale, bias=shift). One kernel and one HBM pass per site instead of two; 1+scale keeps the eager rounding of the [1, D] row, the remaining difference is that scale/shift apply in fp32 to the unrounded normalized value (bf16 rounding-order level, max abs diff ~0.05 on FLUX shapes; not bit-exact). Mounted per batch by the denoising stage exactly like the fused linear+GELU epilogue (sgl-project#33819): sites default off, lossless requests keep the bit-exact reference path. Per-call guard requires B == 1 (the folded affine is a [D] row); nunchaku blocks are untouched.
BBuf
force-pushed
the
k2-flux-modulate-rope
branch
from
August 8, 2026 00:52
bd9b262 to
4c8d148
Compare
Collaborator
Author
BBuf
added a commit
to BBuf/sglang
that referenced
this pull request
Aug 8, 2026
…-exact fused LayerNorm+modulate kernel (H200 1024^2 denoise 6652.6 -> 6573.5 ms, -1.2% lossless) Follow-up to sgl-project#34004, wiring sgl-project#34008's SASS-exact fused LayerNorm+modulate Triton kernel into all five FLUX.1 adaLN site classes (dual-stream norm1 / norm1_context / norm2 / norm2_context + single-stream) through the existing _flux_norm_modulate plumbing: the lossless tier drops the aten LN + fused-modulate pair for one kernel, and the lossy quality="high" LN-affine fold is superseded wherever the kernel verifies (it stays as a fallback for foreign LayerNorm dispatches). Bit-exactness is a property of the live aten dispatch, so every distinct (shape, stride, eps) signature a site emits is verified torch.equal against the eager chain once at runtime and the fast path is disabled permanently on any mismatch -- same safety net as sgl-project#34008's GLM-Image wiring, tightened from one global flag to per-signature. Verification is skipped inside torch.compile tracing and CUDA-graph capture; the warmup pass verifies first. H200 FLUX.1-dev 1024^2 50-step bf16 B=1 seed=42, on the sgl-project#34004+sgl-project#34008 merge base, 2x(10 sequential gens, drop first), pooled median of 18, lossless tier: e2e wall 7428.6 -> 7211.2 ms (-2.9%), server total 6961.3 -> 6858.6 ms (-1.5%), DenoisingStage 6652.6 -> 6573.5 ms (-1.2%). All 40 images across both arms share a single md5 (the same hash the sgl-project#34004 campaign measured). quality="high": DenoisingStage parity with the affine fold (6406.9 vs 6404.3 ms) while the LN sites become lossless again -- PSNR vs lossless is 34.7 dB, exactly the GELU-epilogue-only footprint (sgl-project#34004 measured 34.7 dB for that arm). GPU-only microbenchmark at the site shapes (torch profiler, 100 iters): (1,4096,3072) 35.2 -> 22.2 us, (1,512,3072) 8.6 -> 6.9 us, (1,4608,3072) 40.3 -> 26.1 us vs the eager LN+modulate pair; ~1.1 ms/step summed over the 114 sites, closing against the measured DenoisingStage delta (79 ms / 50 steps).
3 of 5 tasks
saturn-acc
pushed a commit
to saturn-acc/sglang
that referenced
this pull request
Aug 16, 2026
…t, LN-affine folding behind quality=high (H200 e2e -3.5% lossless / -6.9% high) (sgl-project#34004)
6 tasks done
Atituiset
pushed a commit
to Atituiset/sglang
that referenced
this pull request
Sep 10, 2026
…t, LN-affine folding behind quality=high (H200 e2e -3.5% lossless / -6.9% high) (sgl-project#34004)
This was referenced Sep 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Branch:
k2-flux-modulate-rope, three commits onmain(d4be483), standalone — no dependencies. Third wave of the FLUX.1 elementwise cleanup after #33819 (residual-gate fast path + gated GELU epilogue): 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.7fd67406a695675ae120torch.equal)2eda4a7725quality="high"mountMotivation
Per 50-step 1024x1024 FLUX.1-dev image, the diffusers-style adaLN plumbing runs:
norm(x) * (1 + scale) + shiftat 114 sites/step (19 dual blocks x 4: norm1/norm1_context/norm2/norm2_context + 38 single blocks x 1) = three eager elementwise kernels each — a tiny1 + scalerow op plus two full HBM passes over the[1, L, 3072]activation (mul 3.09 ms/step + add 3.14 ms/step + row ops 0.15 ms/step in the S1 trace).torch.cat([cos.float(), sin.float()], -1)on identical inputs — 57 launches/step (19 dual + 38 single), 2,850 per image, all but one producing the same tensor.Modifications
Commit 1 — RoPE hoist (lossless by construction).
FluxTransformer2DModel.forwardbuilds the concatenated fp32 cache once per step (separately for the single-block relayout when SP text sharding is active) and passes the tensor down;FluxAttentionkeeps accepting the raw(cos, sin)tuple for other callers. Every attention consumes the same tensor values as before.Commit 2 — bit-exact fused modulate (lossless,
torch.equal). New JIT CUDA kernelmodulate_scale_shift.cuh(sibling ofresidual_gate_add.cuh, same vec-8 row-broadcast tile structure) computinground(round(x * round(1+scale)) + shift)— each intermediate rounded back to the storage dtype exactly like the eager chain's kernel boundaries, which also structurally blocks FMA contraction. Because every eager op in the chain is elementwise, matching the per-op rounding is sufficient for bit-exactness (no reduction-order risk). Wired unconditionally via localFluxAdaLayerNormZero{,Single}subclasses (same parameters/state-dict as the diffusers parents) and the dual-block norm2 sites; guarded inputs (non-half dtype, B>1 non-contiguous chunk views, misalignment) fall back to the eager expression,torch.compiler.is_compiling()escape hatch as in #33819. Nunchaku branches (scalewithout1+) untouched.Commit 3 — LN-affine folding behind
quality="high". The remaining lossless-path pair per site is the affine-free LayerNorm kernel + the fused modulate (two HBM passes). For high requests the modulate folds into the LN's elementwise affine:layer_norm(x, weight=1+scale, bias=shift)— one aten kernel, one pass.1 + scalekeeps the eager rounding of the[1, D]row; the remaining difference is that scale/shift apply in fp32 to the unrounded normalized value (bf16 rounding-order level, max abs diff ~0.05 on FLUX shapes) — close, but not bit-exact, hence mounted per batch by the DenoisingStage exactly like the fused linear+GELU epilogue (#33819): sites default off, lossless requests keep the bit-exact reference path (mount log:Mounted fused LN+modulate (affine folding) for quality=high). Per-call guard requires B == 1 (the folded affine is a[D]row).Why the LN reduction itself is not fused on the lossless path
#33819 already audited the shared norm+scale/shift fusions for FLUX and dropped them (
norm_scale_shift_nativeis Blackwell-gated; the Tritonselect01layers are fp32 single-pass, not bit-exact). A T10-style (#33734 ERNIE) bit-exact replication was assessed against torch 2.11'svectorized_layer_norm_kernel: unlike the CuTe-DSL RMSNorm T10 replicated (documented ordered fadd chain + butterfly fold), aten's LayerNorm uses per-element Welford updates (mean + delta * (1.f/new_count)), count-weightedcuWelfordCombinemerges, and the accurate multi-instructionrsqrtf— 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), and the lossless tier keeps aten's LN kernel untouched.Speed Tests (H200)
FLUX.1-dev (sha256-verified
unsloth/FLUX.1-devmirror), 1024x1024, 50 steps, bf16, embedded guidance (single forward, B=1), fixed prompt,seed=42. One warmed process, 10 sequential generations, first dropped, median of 9 (#33451/#33536/#33819 protocol), single pinned H200.Attribution closes against kernel microbenchmarks at the real shapes (bf16, 200 iters after warmup):
Summed over sites: RoPE 0.43 ms/step vs measured 0.39 ms/step commit-1 delta; modulate ~4.7 ms/step (micro) vs 3.65 ms/step measured; LN folding ~1.0 ms/step (micro) vs ~0.8 ms/step measured (high-arm delta net of the lossless wins). Microbenchmarks run standalone slightly above the in-graph deltas, as expected for launch-dense eager chains.
Accuracy Tests
test_modulate_scale_shift.py, 12 cases: FLUX shapes, bf16/fp16, batched rows, adaLN chunk views)torch.equalvs eager chain, all passFluxAdaLayerNormZero{,Single}vs diffusers parents, L in {4096, 512, 4608}torch.equal, all outputs)cad50fb5...)test_fused_ln_modulate.py(folded-LN closeness + mount protocol)test_transformer_quant,test_fused_linear_gelu,test_residual_gate_add)quality=high image quality (vs the bit-exact lossless reference, same seed; #33819 table protocol):
Context: the already-merged GELU-epilogue high tier alone measures 34.67 dB / 0.9765 on the bench prompt against the same reference — the LN folding does not degrade the high tier's quality band (this PR high vs main high: 35.64 dB / 0.9786). Sample images:
/data/solmig/k2_runs/{main,mainhigh,k2high,mainref}/.Benchmark configuration
ion8-h200(H200, containersglang-bbuf),CUDA_VISIBLE_DEVICES=2, torch 2.11.0+cu130.unsloth/FLUX.1-devfrom the shared HF cache (byte-identical mirror of the gatedblack-forest-labs/FLUX.1-dev)./data/solmig/prA_work/pra_e2e.py(the [diffusion] FLUX.1 bit-exact residual-gate fast path + tanh-GELU epilogue behind quality=high (H200 e2e -1.1% lossless / -4.3% high) #33819 harness) —DiffGenerator.from_pretrained(model_path, num_gpus=1, local_mode=True),prompt="A futuristic cyberpunk city at night, neon lights reflecting on wet streets",seed=42,width=height=1024,num_inference_steps=50,quality={lossless|high}, 10 iterations, per-iter PNGs for md5/PSNR./data/solmig/k2_main_tree(d4be483), commit-1/data/solmig/k2_rope_tree(7fd67406a6), PR/data/solmig/k2_tree(HEAD). Raw logs and records JSON:/data/solmig/k2_runs/{main,rope,k2,mainhigh,k2high,mainref,k2final}*.[DenoisingStage] finished in X seconds), same drop-first/median-of-9 policy as wall.Side-by-side sample (lossless left — md5-identical to main — vs quality=high right):
🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ⏳ Run #31235036039
Latest PR Test (Extra): ✅ Run #31236056496