You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b33e35e
Browse filesBrowse the repository at this point in the historyBrowse files
- Add fused_rmsnorm_rope_pallas custom Pallas TPU kernel (src/maxdiffusion/kernels/fused_rmsnorm_rope_pallas.py) combining fp32 RMSNorm scaling, complex rotary position embeddings, in-register Q/K prescaling (q * LOG2E, k * scale), and [B, S, H*D] -> [B, H, S, D] head transpose in a single VMEM pass.
- Add TPU-only platform guard in _fused_rope_producer and prevent cuDNN Flash Attention (cudnn_flash_te) from double-scaling prescaled cached keys.
- Eliminate two 387 MB standalone HBM multiply+copy round-trips per self-attention layer (774 MB/layer, 2.48 TB over 40 steps) prior to Ulysses/Ring all-to-all, worth 1.7% (tpu7x) to 3.1% (v6e) of denoise time end to end. The kernel is 0 ULP against a compiled XLA reference in isolation; inlined in the full model it is arithmetically equivalent but not bit-identical (34.1 dB PSNR over a 40-step denoise on v6e).
- Enable in-kernel VMEM transpose in custom splash attention (custom_splash_attention.py) to streamline output relayout across Ulysses sequences.
- Optimize cross-attention projection handling with split_head_dim in WanModel to eliminate unnecessary head transpose barriers.
- Update run_wan_fast_inference.sh with tuned latency-hiding scheduler options, p_state=7 on v7x, environment variable overrides, and optimized collective overlapping.
- Add JAX >= 0.11.0 compatibility shim in __init__.py and generate_wan.py for Flax NNX HiPrimitive deprecation.
- Add comprehensive test suites: fused_rmsnorm_rope_pallas_test.py (including production JIT + shard_map + f32 RoPE + prescale parity, finite/NaN assertions, and true integer ULP metrics), dot_fallback_layout_test.py, and aot_cache_test.py.
Key the AOT cache on the resolved RoPE accumulation mode.
WAN_ROPE_ACCUM changes the lowered graph's rounding but was not part of the Wan
AOT metadata fingerprint, so flipping it could silently reload an executable
compiled under the other rounding mode. Both settings produced identical
metadata.
The underlying problem was a duplicated rule: attention_flax.py computed the
platform default inline, and generate_wan.py did not model it at all. Adding a
second copy to the fingerprint would have reintroduced the same class of bug, so
the rule now lives in one place, resolve_rope_accum(mesh), called by both the
production call site and _build_wan_aot_metadata.
The fingerprint records the RESOLVED mode rather than the raw env var. Recording
the raw value would both over-invalidate (an explicit "f32" on v6e describes the
same executable as the unset default) and fail to describe what was actually
compiled. An unrecognised value now raises instead of being passed through.
Also closes a gap in parity coverage. The prescaled parity test pinned
rope_accum="f32" and checked bit-equality against another Pallas result, so the
combination that actually ships on v7 -- prescale ON, rope_accum="dtype", and the
production 1024 tile -- had no 0-ULP assertion anywhere; a regression in it could
only have surfaced as a quality drop in a 40-step denoise. The added test
resolves the mode the same way production does, uses fused_rope_block_s /
fused_rope_head_block instead of the kernel defaults, and compares against an XLA
reference whose prescale is applied inside the same jit.
Verified on tpu7x-8 and v6e-8:
* 16/16 aot_cache_test, 4/4 production-shape parity tests on both.
* The shipped config is 0 ULP against fused-in-jit, barrier-staged and post-hoc
XLA references alike (0/774,144,000 elements differ), so the strict bound is
measured, not assumed.
* Negative control: forcing WAN_ROPE_ACCUM=f32 on tpu7x fails the new test with
26.4% of elements differing, confirming it discriminates.
* Through the real AOT cache at a fixed commit, the two modes key different
executables and reverting reloads the original rather than recompiling; the
two modes produce different output, which is what made the missing key a
wrong-answer bug rather than a hygiene issue.
* E2E unchanged by this cache-keying change: denoise 96.7s (v7x) and 129.6s
(v6e), with byte-identical output video at a fixed seed for a given resolved
rope_accum mode.
Compare the kernel against a compiled reference, on both sides.
The new parity tests compared an eagerly-dispatched XLA reference against the
compiled kernel. That is not a comparison of the kernel: eager dispatch
evaluates each op separately, while XLA under jit contracts the RoPE
multiply-add into an FMA that rounds once. Measured on v6e at seq=50, the eager
and jitted references disagree on 30.6% of elements, and the kernel matches
whichever one shares its rounding -- exactly, with nothing in between. The
pairing happened to be self-consistent on v6e and tpu7x and was not on the v4-8
CI runner, where it failed with 35.3% of elements differing.
References now go through _compiled_reference and the kernel through
_compiled_kernel, with the mode from resolve_rope_accum(). One test keeps the
eager pairing deliberately: under an identity rotation both modes denote the
same arithmetic, which is the point of that test.
Compiling only the reference was not enough. norm_mode="exact" leaves the fp32
feature-axis reduction in XLA, so the kernel has an XLA prologue of its own, and
eager and jitted reductions diverge at full size: 665/96,768,000 outputs on
tpu7x and 503/96,768,000 on v6e, max |diff| 3.125e-02. Small-shape tests cannot
see it.
Also fixes resolve_rope_accum off TPU. It assumed every non-tpu7x platform
contracts into fp32 FMAs, but XLA:CPU emits the multiply and the add separately;
measured, the eager and jitted references agree to 0 ULP on CPU and only "dtype"
matches either. The f32 default is now gated on the platform actually being a
TPU, which is what makes the interpret-mode tests meaningful off TPU.
Verified: 34/34 kernel tests and 16/16 aot_cache_test on CPU (interpret), tpu7x-8
(resolves to "dtype") and v6e-8 (resolves to "f32"). Negative control: forcing
WAN_ROPE_ACCUM=dtype on v6e fails 7 tests, including every converted one.
On a parity failure the assertion now prints the reference-dispatch x
accumulation-mode grid. A bare "31% of elements differ" cannot distinguish a
real algebra bug from the kernel being built against the wrong rounding
convention for the platform, and those want opposite fixes. If some cell is
0 ULP the kernel is correct and only the resolver is wrong; if none is, the
arithmetic itself disagrees with the producer. That question has to be
answerable from a CI log on a TPU generation that is not available locally.
Scope the bit-exactness claim to the platforms where it was measured.
The v4-8 CI runner reproduces the producer under no accumulation mode at all.
Its grid, at seq=50:
ref:eager vs kernel/dtype 35.30% ref:jit vs kernel/dtype 20.88%
ref:eager vs kernel/f32 39.76% ref:jit vs kernel/f32 30.96%
The kernel is not wrong there. The normalisation is still bit-exact, the drift
is confined to the RoPE combine, and it is at most one bf16 ULP (max |diff|
3.125e-02, exactly ulp(bf16) at that magnitude); v4 rounds `a*cos + b*sin` in a
way neither mode models. Note also that the kernel is dispatch-invariant on v4 --
the eager and jit columns are identical -- so this is not reachable by another
resolver tweak.
Bit-exactness is therefore a per-platform property, and `rope_accum_is_measured`
now says where it holds: tpu7x ("dtype"), v6 ("f32") and XLA:CPU ("dtype"),
which covers both platforms Wan serves on. Tests asserting 0 ULP skip elsewhere
rather than assert something untrue. Tests with a non-zero ULP budget still run
everywhere, so v4 keeps real coverage of the algebra. An explicit
WAN_ROPE_ACCUM counts as measured, so overriding it does not quietly soften the
negative control.
Verified after the change: 34/34 kernel and 16/16 aot_cache_test on tpu7x-8 and
v6e-8, with nothing skipped on either -- the guard does not remove coverage on
the platforms that matter -- and 50/50 on CPU.
Remove the last inline copy of the platform rule. The shard_map production
parity test re-derived "dtype on tpu7x, f32 everywhere else" locally instead of
calling the resolver, so it kept asserting 0 ULP on v4 after the shared rule had
learned better. It now calls resolve_rope_accum(mesh) and is gated the same way.
0 commit comments