Repository navigation
[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) - #33451
Merged
Conversation
Contributor
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
BBuf
force-pushed
the
pr1-vae-gn-silu-kernel
branch
from
August 4, 2026 03:04
13aeacc to
4d8040d
Compare
BBuf
requested review from
AgainstEntropy,
HaiShaw,
ping1jing2 and
yichiche
as code owners
August 4, 2026 03:04
BBuf
force-pushed
the
pr1-vae-gn-silu-kernel
branch
from
August 4, 2026 04:42
4d8040d to
5e4ba24
Compare
…024^2 97.6->29.2 ms, 2048^2 437.2->168.5 ms) The FLUX.2 VAE decoder spends 97.6/437.2 ms per 1024^2/2048^2 image on H200: the fp32-upcast GroupNorm+SiLU chains dominate the profile and the NCHW decoder pays nchwToNhwc/nhwcToNchw transposes around every cuDNN conv. - New channels_last-only two-pass GroupNorm(+SiLU) Triton kernel (kernels/ops/diffusion/triton/group_norm_silu_twopass.py): fp32 statistics, affine folded into per-(batch, channel) scale/shift, native (N, H, W, C) / (N, L, C) rows support, optional SiLU epilogue; 4.8-25.9x vs aten fp32 at FLUX.2 decoder shapes. The existing general triton_group_norm_silu keeps serving the NCHW-contiguous HunyuanVAE / latent-upsampler paths unchanged. - FLUX.2 decoder fast paths (flux2_vae_cuda_opt.py), installed by the existing CudaPlatform.optimize_vae hook: channels_last decoding with the parameter layout swapped at decode entry, GroupNorm+SiLU fusion via the new kernel, nearest-2x upsample+Conv2d folded into ConvTranspose2d(k4,s2,p1), and a layout-safe single-head attention forward with the output projection folded into V. All rewrites are mathematically exact re-associations with lazy fp32 weight folding on first fast-path use; install is all-or-nothing and fail-closed. - Request-scoped gate wired in DecodingStage: quality == "high" (sgl-project#33453) runs the fast paths for the duration of the decode; the "lossless" default runs the original module path bit-for-bit. Validation (H200, frozen decode-input latents, steady-state mean of 5): - decode 1024^2 97.6 -> 29.2 ms (3.34x), 2048^2 437.2 -> 168.5 ms (2.59x); lossless path unregressed (97.1/437.0 ms) and byte-identical PNGs vs origin/main at both sizes; - fp32 ground truth (1024^2): fast-path bf16 error matches baseline bf16 error (mean abs 0.000828 vs 0.000824, p99 0.003051 vs 0.003027); - e2e sglang generate (FLUX.2-klein-4B): --quality high output PSNR 57.2 dB vs the default lossless output at the same seed; - 7 unit tests (kernel numerics/fallback contract, gate dispatch, lazy folding, fold math). Follow-ups land separately: exact chunked D512 attention composition for >=16k-token mid-blocks, and Qwen-Image VAE fast paths.
BBuf
force-pushed
the
pr1-vae-gn-silu-kernel
branch
from
August 4, 2026 04:50
5e4ba24 to
50a60f1
Compare
4 tasks done
4 tasks done
Collaborator
Author
BBuf
added a commit
to BBuf/sglang
that referenced
this pull request
Aug 5, 2026
…tWan e2e 9.611 -> 9.125 s) Revives the sgl-project#30171 kernel behind the request-level quality gate. sgl-project#30171 was closed for a "consistency regression on B200": the fused kernel is numerically faithful (fp32 statistics, eager op boundaries preserved) but not bit-exact vs aten, so it moved the CI golden outputs. The quality tiers from sgl-project#33453 plus the sgl-project#33451 gate infrastructure remove that constraint: the kernel now runs only for requests that opt in with quality == "high"; the "lossless" default runs the original module path bit-for-bit (byte-identical harness outputs vs main). - New channels_last_3d Wan RMSNorm(+SiLU) Triton kernel (kernels/ops/diffusion/triton/wan_rmsnorm_silu.py): one program per (b, t, h, w) pixel channel row, fp32 statistics, WanRMS_norm eager op boundaries preserved per step. vs sgl-project#30171: supports the autocast dtype split (half activations + fp32 affine, output promoted to fp32 like aten) used by the bf16-decode Wan2.1 family; returns None on unsupported inputs (callers fall back); t/h/w are runtime args so the causal chunked decode does not recompile per chunk shape. - Wan decoder fast path (wan_vae_cuda_opt.py), installed by CudaPlatform.optimize_vae: FusedWanRMSNormSiLU wrappers on every residual-block norm1/norm2 + the decoder output head (29 sites on the FastWan2.2 VAE), dispatching on the shared sgl-project#33451 VaeFastPathGate; the DecodingStage wiring is reused unchanged. Install is all-or-nothing and fail-closed. Eager decodes only: under torch.compile Inductor already fuses the chain and the opaque custom op measures slightly slower (decode 4.90 -> 4.98 s), so compiled decodes keep the Inductor path for both tiers (verified at parity: 8.969 vs 8.948 s e2e, decode 4.90 both). Validation (H200 single GPU, seed 42): - kernel microbenchmark at captured production decoder shapes: 1.40-4.48x (fp32 FastWan 720p rows), 1.23-3.20x (bf16+fp32-affine Wan2.1 480p rows) vs the eager aten chain; - component harness (frozen production decode-input latents): FastWan 720p decode 5910 -> 5458 ms (-7.6%), Wan2.1 480p decode 1839 -> 1620 ms (-11.9%); gate-off byte-identical (sha256) to main and unregressed (5918 / 1833 ms on main); - e2e (one warmed process, sequential requests, median of 5 steady): FastWan2.2-TI2V-5B 720p x 81f eager 9.611 -> 9.125 s (-5.1%, DecodingStage 5.77 -> 5.11 s); 50-step models dilute the same decode win to ~1-2% e2e (50-step CFG denoise at the measured 0.315 s/step ~= 31.5 s vs the 0.66 s decode delta; a live 50-step Wan2.1-T2V run is currently blocked by the unrelated UMT5 qkv_proj loading regression on main); - quality=high vs lossless outputs: PSNR 36.1-39.7 dB (bar > 25), SSIM >= 0.956 over 3 prompts x 81 frames; fp32 ground-truth error of the fast path statistically matches the baseline (mean abs 0.001003 vs 0.000959); 8 unit tests. Stacked on sgl-project#33451 (pr-flux2-vae-fastpath); depends on it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 6, 2026
Merged
saturn-acc
pushed a commit
to saturn-acc/sglang
that referenced
this pull request
Aug 16, 2026
…024^2 97.6->29.2 ms, 2048^2 437.2->168.5 ms) (sgl-project#33451)
Atituiset
pushed a commit
to Atituiset/sglang
that referenced
this pull request
Sep 10, 2026
…024^2 97.6->29.2 ms, 2048^2 437.2->168.5 ms) (sgl-project#33451)
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:
pr-flux2-vae-fastpath, single commiteada083612onmain(7ba393d, post-#33453), standalone — no dependencies. 5 files, +920/−28.Motivation
FLUX.2 image generation spends 97.6 ms (1024^2) / 437.2 ms (2048^2) per image in VAE decode on H200. Profiling the decoder (diffusers
Decoder) shows two structural problems:GroupNorm -> SiLUchains upcast to fp32 under autocast and are ~46% of decode time — and they get worse if you naively switch to channels_last, because aten GroupNorm is ~2x slower on NHWC and the existingtriton_group_norm_silukernel forces a.contiguous()(NCHW) round-trip on channels_last inputs;AttnProcessor2_0forward is layout-unsafe (calls.viewon the 4D activation).This PR adds (a) a layout-aware two-pass GroupNorm(+SiLU) Triton kernel and (b) a request-scoped, quality-gated FLUX.2 VAE decoder fast path built on it. All decoder rewrites are mathematically exact re-associations of the original operators (weight folding is done lazily in fp32 on first fast-path use).
Modifications
1. Kernel — new
python/sglang/kernels/ops/diffusion/triton/group_norm_silu_twopass.py(+232):group_norm_silu_4d/group_norm_silu_rowsreturnsNonefor unsupported inputs (non-CUDA, grad enabled, non-pow2 C, C > 2048, not channels_last); callers must fall back.group_norm_silu.py(any channels-per-group, always-SiLU, NCHW via.contiguous()), which keeps serving the HunyuanVAE / latent-upsampler paths unchanged; on NCHW the two are a wash (63–219 us vs 81–210 us), so this kernel deliberately has no NCHW variant — gate-off decodes run the original aten path, never this kernel. Documented in the module docstring.2. FLUX.2 fast paths + gate —
runtime/models/vaes/flux2_vae_cuda_opt.py(+421):CudaPlatform.optimize_vae(existing platform hook called by the VAE loader, +22 inplatforms/cuda.py) installs always-present fast-path wrappers on the FLUX.2 decoder; each wrapper's forward dispatches on a shared per-VAEVaeFastPathGate(a module-local bool holder published on the VAE as_sgl_vae_fast_path_gate).quality == "high"([diffusion] Restrict request-level quality to two validated tiers: lossless (default) and high #33453 tier) runs the fast paths; the"lossless"default runs the original module path bit-for-bit (verified byte-identical below), so golden-output CI is unaffected.conv_norm_out/conv_acttail).A @ (V W_v^T + b_v) W_o^T + b_o == A @ (V W'^T) + b'). Attention stays on the stock SDPA backend here; the exact chunked D512 composition for >= 16k tokens is a follow-up PR (recovers 168.1 -> ~147 ms at 2048^2).3. DecodingStage wiring —
pipelines_core/stages/decoding.py(+39/−27, mostly the try/finally re-indent):DecodingStage.forwardsets the gate for the duration of a decode iff the request'squalitysampling param is"high"and always restores it in afinally; VAEs without installed wrappers have no gate attribute (no-op).Follow-up PRs: (1) chunked D512 attention composition; (2) Qwen-Image VAE fast paths.
Speedup
All numbers H200, bf16 decode. Three levels:
Kernel microbenchmark (FLUX.2 VAE decoder norm shapes, G=32, us per call, mean of 50; before = aten fp32 GroupNorm+SiLU on channels_last, after = new kernel on channels_last):
(HunyuanVAE-style 5D shapes are untouched — no call site of the existing kernel changes.)
Component harness (frozen decode-input latents, steady-state mean of 5 decodes, this branch):
Lossless-path timing on this branch is unregressed (97.1 / 437.0 ms vs main baseline 97.6 / 437.2 ms); the minimized branch reproduces the fast path at 29.2 / 168.5 ms (run-to-run noise of the numbers above).
End-to-end latency (
sglang generate, FLUX.2-klein-4B, H200, prompt "A futuristic cyberpunk city at night, neon lights reflecting on wet streets", seed 42,--dit-layerwise-offload false --warmup --save-output). Metric = per-request wall-clock measured client-side around the full request round-trip ("Pixel data generated successfully in X seconds"), which includes the VAE decode GPU time, the final sync, and the PNG write. Per cell: one warmed process serving sequential identical requests, first request dropped (absorbs the post-warmup pickup gap), median of >= 5 clean steady-state requests (min–max); requests hit by neighbor-GPU interference on the shared box are excluded via a denoise-time stability filter (DenoisingStage is untouched by this PR, so its per-request time must be flat):--quality highThe e2e deltas match the component-harness decode deltas within run-to-run noise (a 4-step klein request is denoise-dominated, so decode's share of the request bounds the relative win). The component harness table above stays the authoritative decode-time measurement.
Cross-checks: (1) the pipeline stage-timer total (
total_duration_ms, printed as "Warmed-up request processed in X") stops at decode kernel launch without a CUDA sync, so it is decode-blind by construction — and it is flat between the two modes (medians of 5 independent cold-process runs per cell: 0.41 / 0.41 s at 1024^2, 1.47 / 1.46 s at 2048^2; perf-dump totals 407 / 407 ms and 1454 / 1457 ms), which independently confirms the gate adds no overhead to TextEncoding/Denoising or host-side dispatch. (2)--quality highis accepted end-to-end by the generic sampling-param validation, and the log confirms the fast path engages only there — "FLUX.2 VAE: decoder switched to channels_last (NHWC) layout" appears once per process (lazy fold, not per request) and never in lossless runs.End-to-end quality validation (maintainer bar for
quality=high: PSNR > 25 dB vs the lossless output, same seed and prompt, saved PNGs):All pairs sit 30+ dB above the bar (max per-pixel diff 4–6/255): the deviation is bf16 rounding-order level, not a visible quality change; the fp32 ground-truth check below confirms the fast path is not statistically further from an fp32 reference than the baseline bf16 decoder.
Accuracy
origin/mainbaseline (verified withcmpat 1024^2 and 2048^2), so golden-output CI is unaffected.--quality highvs default output PSNR = 56.7–59.1 dB and SSIM >= 0.999 across 3 prompts / 2 resolutions (>= 55 dB internal bar; maintainer bar PSNR > 25 dB — see the e2e quality table above).test/registered/kernels/ops/diffusion/test_flux2_vae_fastpath.py(+206, one file, 7 cases: channels_last kernel numerics fp32/bf16 vs fp32 reference, rows variant as used by attention, unsupported-inputNonecontract incl. NCHW; gate-offtorch.equal+ no eager folding + fast-path PSNR for GroupNorm+SiLU and fused upsample; attention V/proj fold exactness). 7 passed on H200.Checklist
Sample outputs (lossless vs
--quality high)FLUX.2-klein-4B, seed 42. Left: default (
lossless, bit-exact reference path). Right:--quality high(fast path). Full-resolution originals; PSNR/SSIM per pair in the quality table above.--quality high🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ⏳ Run #30906077610
Latest PR Test (Extra): ⏳ Run #30906077382