Repository navigation
Conversation
dbd6ace to
7a6e0e6
Compare
There was a problem hiding this comment.
Code Review
This pull request introduces a fused RMSNorm + RoPE + head-transposition Pallas kernel for TPU, integrates it into the Flax attention pipeline, and implements several optimizations such as in-kernel VMEM transpose and Classifier-Free Guidance (CFG) before unpatchify. Feedback on these changes highlights critical runtime issues, including an AttributeError from using jax.shard_map instead of jax.experimental.shard_map.shard_map and a potential NameError due to an undefined _warn_once helper. Additionally, the reviewer recommends adding defensive checks to validate the sequence length of freqs_cis and removing redundant JAX compatibility shims that are already handled in the package initialization.
5b75348 to
82681ec
Compare
82681ec to
b33e35e
Compare
b33e35e to
7740dcf
Compare
7740dcf to
5c0f8bb
Compare
5c0f8bb to
aee8c4a
Compare
2f8b92d to
a507d0d
Compare
6ac9049 to
96dd50f
Compare
96dd50f to
359249b
Compare
593e337 to
4ebc6fd
Compare
4ebc6fd to
5a1fa32
Compare
a75047a to
0b1315d
Compare
0b1315d to
77afa86
Compare
77afa86 to
2e70bc8
Compare
2e70bc8 to
973594c
Compare
mbohlool
left a comment
There was a problem hiding this comment.
Approving once the inline items are addressed; the Pallas producer matches the unfused reference op for op, and tiling, tails and VMEM look right.
…, not env
Fused producer (kernels/fused_rmsnorm_rope_pallas.py)
- A Pallas kernel computing FP32 RMSNorm + RoPE + the [B,S,H*D] -> [B,H,S,D]
head transpose for self-attention Q/K. It optionally folds log2(e) into Q
and 1/sqrt(d) into K.
- norm_mode="exact" leaves the feature reduction to XLA. It is 0 ULP
against the separately jitted XLA producer (`fused_rmsnorm_rope`).
- norm_mode="fused" does the reduction in-kernel. It stays within 2x
pair-relative bf16 eps.
- rope_accum ("dtype" | "f32") matches how the local compiler rounds the
RoPE multiply-add. "auto" uses the per-platform measured mode: tpu7x
"dtype", v6e "f32".
- Off by default in the YAML (`use_fused_rope_kernel: False`) and in the
launcher's generic profile. Once inlined into the 40-layer graph, XLA rounds
the unfused producer differently, so the output is equivalent but not
identical (720p/81f, 40 steps: pre-decode bf16 latent PSNR is 53.8 dB after
1 step and 34.1 dB after 40 steps on v6e seed 12345; post-VAE decoded uint8
video PSNR / SSIM with peak=255 at stack tip is 25.71 dB / 0.9125 on v6e
seed 777, 17.92 dB / 0.6556 on v6e seed 12345, and 17.69 dB / 0.6918 on
tpu7x seed 12345).
- Fails closed (`ValueError`) when `use_fused_rope_kernel=True` is explicitly
requested on non-TPU meshes, unmeasured TPU generations, sharded feature
axes, `dim_head % 128 != 0`, or uneven sequence splits.
- `run_wan_fast_inference.sh` turns it ON in the v6e and v7 profiles. Measured
at the stack tip (Wan 2.2 T2V-A14B, 720p/81f/40 steps, warm AOT, CP=4, DP=2),
kernel on vs off:
v6e-8 (DVFS unpinned): denoise 124.5s - 125.0s (generate 126.6s - 127.5s) vs 131.2s off
tpu7x-8 (DVFS unpinned): denoise 104.9s (generate 107.8s) vs 106.6s (generate 109.6s) off
tpu7x-8 (DVFS pinned): denoise 91.7s (generate 94.6s) vs 93.4s (generate 96.4s) off
- Isolated speed, one v6e chip, jax 0.11.2, 40 heads, d=128, bf16, exact
mode, block_s=1024, median of 50 runs: per-shard seq 9450 takes 0.964 ms
vs 1.789 ms for XLA (1.86x); seq 18900 takes 1.953 ms vs 4.018 ms (2.06x).
- VMEM budget: 64 MiB is requested only when every TPU in the mesh is a v6e or
tpu7x (the validated generations).
Wan graph-changing switches
- `wan_rope_norm_mode`, `wan_fuse_qk_prescale`, `wan_splash_transpose_out`,
`wan_cfg_before_unpatchify`, `wan_cross_attn_prescale_kv` and `wan_rope_accum`
are pure YAML/config keys declared in every `base_wan*.yml` with the code
default and resolved at model-build time via `from_config` /
`attention_config_entries(config)` (no environment-variable fallback or
process-global store). Every value lives on the GraphDef and in the AOT key.
- `wan_cfg_before_unpatchify` (default True) applies classifier-free guidance
on packed tokens before unpatchify instead of after unpatchify.
- splash `transpose_out`: an in-kernel output layout [H,S,D] instead of
[H,D,S], for the custom splash kernel and the non-ring Ulysses wrapper. Off
by default.
Other changes
- `split_head_dim` is plumbed from the config into `WanModel` and its
attention layers (`False` in the six `base_wan*.yml` files).
- `_apply_attention_dot`: handles prescaled Q/K (`qk_prescaled` /
`k_prescaled` / `use_base2_exp`) and refuses prescaled Q/K for unsupported
kernels and head-local SVG attention.
Tests
- `fused_rmsnorm_rope_pallas_test.py`: parity grid, fail-closed guards,
backward, VMEM resolver, `FlaxWanAttention` sequence-sharded integration, and
`transpose_out` tests; runs in GitHub Actions on CPU and unvalidated TPUs via `interpret=not _on_validated_tpu()`; bit-exact (ULP-pinned) assertions skip on unvalidated TPUs because interpret mode on a TPU backend is not rounding-faithful (XLA excess precision elides the kernel's bf16 round trips; reproduced on v6e and documented in the test module header).
- `dot_fallback_layout_test.py`, `wan_runtime_options_test.py`, `aot_cache_test.py`.
Summary
Adds a fused RMSNorm + RoPE + head-transpose Pallas producer for Wan self-attention Q/K, and moves Wan's graph-changing switches out of environment variables into config.
What's in it
kernels/fused_rmsnorm_rope_pallas.py): computes FP32 RMSNorm + RoPE + the[B,S,H*D] → [B,H,S,D]transpose, with optional log2(e) / 1/√d prescale folding.norm_mode="exact"is 0 ULP against the separately jitted XLA producer (fused_rmsnorm_ropefromfeat/fused-rmsnorm-rope-xla).Off by default in the yml (
use_fused_rope_kernel: False) and in the launcher's generic profile: once inlined into the 40-layer graph, XLA rounds the unfused producer differently, so the 40-step v6e trajectory diverges (~15 dB PSNR against the XLA-path video, visually clean; bit-identical on tpu7x).Turned on in the launcher's v6e and v7 profiles. End-to-end latency (Wan 2.2 T2V-A14B, 720p / 81 frames / 40 steps,
CP=4, DP=2, warm AOT, DVFS unpinned):Set
USE_FUSED_ROPE_KERNEL=falseto turn it off.wan_rope_norm_mode,wan_fuse_qk_prescale,wan_splash_transpose_out,wan_cfg_before_unpatchify,wan_cross_attn_prescale_kvandwan_rope_accumare yml keys passed viaattention_config_entries(config)acrossWanPipeline, VACE, Animate, andwan_block_benchmark.wan_cfg_before_unpatchify(on by default): applies CFG before unpatchify. A compiled parity test shows bit-identical output in f32 everywhere and in bf16 on CPU/v6e, and within 1 bf16 ULP on tpu7x.Tests
fused_rmsnorm_rope_pallas_test.py(38 test methods / 60 parameterized cases),wan_runtime_options_test.py(11), plus updates todot_fallback_layout_test.py(12) andaot_cache_test.py(45).rope_accumskip on unvalidated TPUs (vmem_limit_is_validated,rope_accum_is_measured).GITHUB_ACTIONS=true(88 of 283 cumulative); guards, VMEM resolver, runtime options, AOT cache, and dot-fallback layout tests run in CI.run_wan_stack_tests.sh, 575.2 s).Stack: #477 → #507 → #478 → #508 → #479 → #488 → #509 → #510 → #511 → #491