Repository navigation
[diffusion] Wan VAE RMSNorm+SiLU fusion behind quality=high (H200 FastWan2.2 e2e 9.611 -> 9.125 s) - #33546
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
a829fc5 to
7b2d288
Compare
…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>
4a50958 to
0d35fb3
Compare
…st-path gate component-accuracy transfers weights by parameter name; nesting the whole WanRMS_norm as a submodule renamed ...norm1.gamma to ...norm1.norm.gamma and failed the >=98% match check. Register gamma/bias directly on the wrapper (FQNs unchanged) and inline the bit-identical off-path op chain, mirroring FLUX.2's FusedGroupNormSiLU. Also dedupe VaeFastPathGate/GATE_ATTR by importing them from flux2_vae_cuda_opt and trim its docstrings. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Fixed the 🤖 Generated with Claude Code |
…tWan2.2 e2e 9.611 -> 9.125 s) (sgl-project#33546) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…tWan2.2 e2e 9.611 -> 9.125 s) (sgl-project#33546) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…tWan2.2 e2e 9.611 -> 9.125 s) (sgl-project#33546) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Branch:
pr-wan-vae-norm-silu-quality-high, single commit rebased on main (now that #33451 is merged). It reuses theDecodingStagegate wiring from #33451 (_sgl_vae_fast_path_gate, duck-typed) unchanged; the gate class itself is a deliberate inline copy, so this PR imports nothing from the FLUX.2 fast-path module. 4 files, +472/−4.Motivation
This revives #30171 ("[diffusion] Fuse Wan VAE RMSNorm SiLU"), which fused the Wan VAE
RMSNorm -> SiLUchains with a Triton kernel and was closed with the stated reason "consistency regression on B200": the kernel is numerically faithful (fp32 statistics,WanRMS_normeager op boundaries preserved per step) but not bit-exact vs aten — a different reduction and SiLU path — so it moved the CI golden outputs. That is a policy constraint, not a numerics bug, and the infrastructure that has landed since removes it:qualityto two validated tiers:"lossless"(default) and"high";DecodingStagewiring:quality == "high"enables installed fast paths for the duration of a decode, the"lossless"default runs the original module path bit-for-bit.This PR puts the #30171 kernel behind that gate. The
"lossless"default is byte-identical tomain(verified below), so golden-output CI is unaffected; only requests that opt in with--quality highrun the fused kernel.The workload motivation is unchanged from #30171: FastWan2.2-TI2V-5B is a 3-step DMD video model, so VAE decode dominates the request (DecodingStage is 5.77 s of the 9.61 s request, 60%, at 720p x 81 frames), and the decoder's
WanRMS_norm -> SiLUchains (norm1/norm2 in every residual block + the output head, 29 sites) each run as ~5 strided aten kernels over up-to-1.8 GB activations.Modifications
1. Kernel — new
python/sglang/kernels/ops/diffusion/triton/wan_rmsnorm_silu.py(+196; the #30171 kernel migrated to thekernels.opsnamespace with three deliberate changes):F.normalize(x, dim=1) * scale * gamma [+ bias]+ SiLU: fp32 norm statistics, each step materialized at the same dtype boundary asWanRMS_norm.forwardeager, SiLU computed in fp32.vae_decode_precision="bf16", fp32 VAE weights) actually runs. aten promotes to fp32 at the* gammaboundary; the kernel reproduces that promotion and returns fp32. Without this the kernel would never engage on Wan2.1-style decodes (FastWan2.2 decodes in fp32, where the uniform-dtype path applies).Nonefor unsupported inputs (non-CUDA, grad mode, unsupported dtype combination, non-channels_last_3d layout, C > 1024) instead of raising — callers must fall back (same contract as [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's kernel).t/h/wsizes are runtime args instead oftl.constexpr, so the causal chunked Wan decode (t=1 head chunk + t=2/4 steady chunks per temporal stage) does not trigger per-chunk-shape recompiles.2. Wan fast path + gate — new
runtime/models/vaes/wan_vae_cuda_opt.py(+159), wired via the existingCudaPlatform.optimize_vaehook (+7/−4 inplatforms/cuda.py):maybe_optimize_wan_vaewraps every decoderWanRMS_norm -> SiLUchain (residual-block norm1/norm2 + the decoder output head; 29 sites on the FastWan2.2 VAE) in aFusedWanRMSNormSiLUmodule dispatching on a per-VAEVaeFastPathGatepublished as_sgl_vae_fast_path_gate— a deliberate inline copy of the [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 gate (DecodingStageduck-types the attribute), keeping the two VAE fast-path modules independent. Gate on runs the fused kernel (with theNonefallback); gate off runsF.silu(self.norm(x)), bit-identical to the originalnorm + nn.SiLUpair.DecodingStagegate wiring is reused from [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 unchanged (zero changes todecoding.pyhere).WanRMS_norm/nn.SiLUstructure, nothing is installed and every request runs the unmodified decoder. The encoder is untouched (the gate is decode-scoped).--enable-torch-compileInductor already fuses the norm+SiLU chain when it compiles the decode, and routing it through an opaque custom op instead measured slightly slower (compiled FastWan decode 4.90 -> 4.98 s). The wrapper therefore steps aside viatorch.compiler.is_compiling()and compiled decodes keep the Inductor path for both quality tiers — verified at parity below.3. Gate sharing & parameter FQNs —
FusedWanRMSNormSiLUregistersgamma/biasdirectly on the wrapper (parameter names stay...norm1.gamma, so by-name weight transfer and strictstate_dictloads are unaffected) and inlines the bit-identical off-path op chain, mirroring FLUX.2'sFusedGroupNormSiLU;VaeFastPathGate/GATE_ATTRare imported fromflux2_vae_cuda_optinstead of duplicated (plus docstring trims there, no behavior change).4. Tests — new
test/registered/kernels/ops/diffusion/test_wan_vae_fastpath.py(+70, 3 cases): kernel numerics vs the eager chain in both production precision regimes (FastWan2.2 fp32-uniform; Wan2.1 bf16 x + fp32 affine with the aten fp32 promotion), each with and without bias; wrapper gate dispatch (gate offtorch.equalvs the original module chain, gate on routes to the fused kernel). 3 passed on H200.Applicability
The e2e win is proportional to VAE decode's share of the request, so it is workload-dependent by construction:
main(unrelatedqkv_projweight-mapping regression), which blocks the whole pipeline; the Wan2.1 decode-level win is still measured directly in the harness table below (-11.9%).--enable-torch-compile): intentionally unaffected in both tiers (see above).Speedup
All numbers H200 (single GPU, GPU-idle box checks around each run), this branch, seed 42. Three levels:
Kernel microbenchmark (real decoder norm shapes captured from production decodes via forward hooks; us per call, median of 13 rounds x 20 calls; aten = the eager
F.silu(F.normalize(x, 1) * scale * gamma)chain at production dtypes):Component harness (frozen decode-input latents — FastWan captured from a production 720p x 81f TI2V request after
scale_and_shift+preprocess_decoding, Wan2.1 fixed-seed synthetic at the production 832x480 x 81f latent shape; production decode replicated: fp32 decode for FastWan, bf16-autocast for Wan2.1; steady-state mean of 5):"byte-identical" = sha256 of the full fp32 [0,1] output tensor equals the
mainbaseline decode of the same latents.End-to-end latency (#33451 protocol: one warmed
DiffGeneratorprocess serving sequential identical requests, per-request wall clock, first request dropped, median of 5; FastWan preset prompt/image, 1280x720, 81 frames):--quality high--enable-torch-compile* max includes one late-compile-tail request; decode-stage times are flat at 4.90 s in both compiled modes (and were 4.98–5.07 s before the
is_compilingbypass was added, i.e. the bypass turns a small compiled regression into exact parity).Stage cross-check for the eager rows:
DecodingStage5.77 -> 5.11 s (-11.5%) whileDmdDenoisingStageis flat (0.930 vs 0.932 s) — the entire e2e delta is decode, as expected.Accuracy
mainbaseline for both the FastWan (fp32) and Wan2.1 (bf16-autocast) workloads, and gate-off timing is unregressed (5910 vs 5918 ms / 1839 vs 1833 ms). Golden-output CI is unaffected.quality=high: PSNR > 25 dB vs the lossless output, same seed and prompt; per-frame metrics over all 81 frames decoded from the saved videos):All frames sit 11+ dB above the bar, in line with #30171's own 37.6–43 dB frame range. 4 side-by-side lossless-vs-high sample frame pairs:
/tmp/prW_imgs/.Checklist
Sample frames (left: lossless, right:
--quality high)🤖 Generated with Claude Code
CI States
Latest PR Test (Base): ⏳ Run #30988802268
Latest PR Test (Extra): ✅ Run #31007345865