Skip to content

feat(wan): fast serving with persistent AOT caching and tuned inference recipe - #479

Open
Perseus14 wants to merge 1 commit into
feat/ring-attentionfrom
feat/wan-fast-serving
Open

Perseus14 wants to merge 1 commit into
feat/ring-attentionfrom
feat/wan-fast-serving

Conversation

@Perseus14

@Perseus14 Perseus14 commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Stacked on top of #478 (feat/ring-attention).

Delivers production-grade fast serving for Wan 2.2 T2V-A14B on Cloud TPU:

  • Persistent AOT Compilation Caching: Implements persistent AOT disk caching for transformer forward passes and VAE decoding, slashing warm startup overhead (compile: 76.6s cold → 8.3s warm on tpu7x-8; 67.6s cold → 8.65s–12.2s warm on v6e-8).
  • Hardened AOT Cache Verification:
    • Computes a SHA-256 content digest (_compute_wan_source_hash()) across 32 Wan/attention/kernel Python source files and includes --untracked-files=no in get_git_commit_hash so code edits automatically invalidate stale caches.
    • Moves _align_inputs outside the broad try/except block in aot_cache.py so input sharding mismatches fail loudly instead of silently triggering recompilation.
  • Tuned Serving Recipe (end_to_end/tpu/run_wan_fast_inference.sh):
    • Platform-specific profiles for Cloud TPU v6e (Trillium, $U=4, R=1$, BQ=9472, BKV=1024, BKV_COMPUTE=512, BKV_COMPUTE_IN=512) and Cloud TPU v7 (tpu7x, $U=2, R=2$, BQ=6400, BKV=2048, BKV_COMPUTE=2048, BKV_COMPUTE_IN=2048), and tuned XLA scheduling flags.
  • v7x-8 & v6e-8 HLO/LLO Optimizations:
    1. Merged Pre-A2A Norm Reduction & Scalar vn_local (_ring_fixed_m_norms_pre_a2a): Folds the K max-norm and 0-D scalar V max-norm (vn_local = (value.astype(jnp.float32) ** 2).max()) into the single (ulysses, ring) pmax and removes the ring-axis all_gather and per-head dynamic_slice on vn_global, eliminating XLA relayout barriers inside the QKV projection fusion region and saving ~3.8s of compute fusion time.
    2. Hoisted Scalar-Prefetch Metadata (_lse_scan): Replaces per-hop dynamic indexing (my_ring_index - hop) with hoisted mk_global_sq, allowing the ~190 MiB K/V ppermute inside lax.cond to overlap kernel execution (collective-permute-done: 2405.4 ms → 47.3 ms; output all-to-all BW: 173 GiB/s → 535 GiB/s), saving ~5.7s denoise time.
    3. Unpadded K/V & Ragged KV Tail Slicing for $R &gt; 1$ (kv_pad_size = 1): Eliminates explicit jnp.pad HBM copies (37,800 -> 38,912), reduces ppermute ICI payload by 1,112 tokens per shard (−2.86%), and slices the 19th KV block at slice_k_len = 936 <= 1024, skipping 384,000 inner VPU/MXU loop iterations across 40 steps (saving 0.4s denoise and 2.9s cold compile time with 100% bit-identical output).
    4. 6D Unpatchify Transpose (p_t == 1, transformer_wan.py): Collapses the unitary temporal patch axis $p_t=1$ during final projection unpatchify (8D stride permutation (0, 7, 1, 4, 2, 5, 3, 6) $\rightarrow$ 6D transpose (0, 6, 1, 2, 4, 3, 5)), saving ~0.17s with 100% bit-exact output (max(abs(diff)) == 0.0).

Verified End-to-End Latency Benchmarks (40 steps, 720p, 81 frames, identical configs)

Both platforms were benchmarked end-to-end on full 40-step Wan 2.2 T2V-A14B inference against origin/main (1bc54811) under identical configurations (vae_decode_chunk=1, vae_spatial=8):

1. Cloud TPU v6e-8 (Profile v6e, $U=4, R=1, B_Q=9472, B_{KV}=1024, B_{KV_COMPUTE}=512, CP=4, DP=2$)

Metric origin/main (1bc54811) PR #479 (a48e0815) Delta / Speedup
Total Inference 156.4 s 138.4 s (138.36 s) −18.0 s (−11.5% faster)
Denoise Total (40 steps) 153.3 s (3.832 s/step) 136.2 s (3.405 s/step) −17.1 s (−11.2% faster)
VAE Decode 2.9 s 0.6–0.7 s 4.5× faster
Compile (Cold / Warm AOT) 152.0 s 67.6 s / 12.2 s (8.65 s best) 12.5× faster warm startup

2. Cloud TPU v7 (tpu7x-8) (Profile v7, $U=2, R=2, B_Q=6400, B_{KV}=2048, B_{KV_COMPUTE}=2048, CP=4, DP=2$)

Metric origin/main (1bc54811) PR #479 (a48e0815, Default) PR #479 (a48e0815, p_state 7) Speedup vs origin/main
Total Inference 119.8 s 116.9 s (115.2 s best) 102.1 s (101.86 s best) −4.6 s Default / −17.7 s (−14.8%) p_state 7
Denoise Total (40 steps) 119.0 s (2.975 s/step) 112.8 s (2.820 s/step) 99.1 s (2.478 s/step) −6.2 s (−5.2%) Default / −19.9 s (−16.7%) p_state 7
VAE Decode 0.8 s 0.8 s 0.7 s −0.1 s faster
Compile (Cold / Warm AOT) 97.6 s / 15.2 s 94.7 s / 8.3 s 76.6 s / 8.3 s 11.8× faster warm startup
Video SHA-256 (seed=12345) 9fb82850... 9530b119ab439714... 9530b119ab439714... 100% Bit-Identical vs Unpadded Ref

Note: v7x-8 denoise numbers verified across multiple warm & cold repetitions on two independent tpu7x-8 hosts (europe-west2-a and us-central1-c), both with default DVFS and with EXTRA_LIBTPU="--xla_tpu_dvfs_p_state=7".

XProf Collective & Compute Profile Breakdown (tpu7x-8, 40 steps)

HLO / LLO Stage origin/main Unpatched PR PR + Merged pmax + Hoisted Metadata PR Final (a48e0815, Default / p_state 7)
collective-permute-done (2-step) 38.0 ms 2399.7 ms 47.3 ms 47.3 ms (−2.86% payload)
Output all-to-all BW 506 GiB/s 173 GiB/s 535 GiB/s 535 GiB/s
40-step Denoise 119.0 s 124.0 s 114.8 s 112.8 s (Default) / 99.1 s (p_state 7)

Numerical Accuracy & Disclosures

  • Pixel-level accuracy vs Exact Ground Truth (ulysses_ring_custom online softmax without fixed-m, 40 steps, 81 frames, seed=12345):
    • Previous PR revision vs Exact: 17.59 dB PSNR (mean abs diff 16.86)
    • This revision (a48e0815) vs Exact: 20.84 dB PSNR (mean abs diff 10.52 — +3.25 dB closer to exact online softmax, 38% lower error)
  • Technical Disclosures:
    1. Because the hybrid fallback branch (_lse_scan) evaluates per-hop fixed-m eligibility against the ring-wide max K norm (mk_global_sq) rather than the per-hop shard norm, the per-hop gate is strictly more conservative and numerically closer to exact online softmax (+3.25 dB PSNR).
    2. Accuracy/PSNR measurements above were evaluated on seed=12345.
    3. per_q_block=True remains supported and tested in unit tests, but requires a dedicated flash block size tile sweep before enabling in production on v7x-8.

Verification

  • Unit Test Suites:
    • pytest src/maxdiffusion/tests/ring_fixed_m_test.py: 37 passed, 1 skipped in 261.62s on tpu7x-8 (including test_gqa_ulysses_ring_custom_fixed_m_ragged).
    • pytest src/maxdiffusion/tests/custom_splash_fixed_m_test.py: 23 passed.
    • pytest src/maxdiffusion/tests/wan/wan_transformer_test.py: 58 passed, 1 skipped.
    • pytest src/maxdiffusion/tests/attention_config_guards_test.py: 16 passed.
  • Rebased cleanly on latest origin/main (1bc54811).
  • Linting: pyink --pyink-indentation=2 --line-length=125 and ruff check clean.

@Perseus14
Perseus14 requested a review from entrpn as a code owner September 13, 2026 06:08
@github-actions

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several optimizations and features for Wan model inference, including a fast-path signature cache in the AOT cache, support for fused LayerNorm and AdaLN kernels, and updated configuration options. The review feedback highlights a potential concurrency issue in the signature cache that could lead to race conditions, suggests moving inline imports of the fused kernel to the top of the file to reduce overhead in hot paths, and recommends avoiding os.path.join on GCS URIs to ensure cross-platform safety.

Comment thread src/maxdiffusion/aot_cache.py Outdated
Comment thread src/maxdiffusion/models/wan/transformers/transformer_wan.py Outdated
Comment thread src/maxdiffusion/models/wan/transformers/transformer_wan.py Outdated
Comment thread src/maxdiffusion/models/wan/transformers/transformer_wan.py Outdated
Comment thread src/maxdiffusion/generate_wan.py Outdated
Comment thread src/maxdiffusion/generate_wan.py Outdated
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from c9dc7b2 to 80719d9 Compare September 13, 2026 06:16
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from 80719d9 to 585247a Compare September 13, 2026 06:25
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from 585247a to 2ee9c66 Compare September 13, 2026 06:30
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from 2ee9c66 to 8756432 Compare September 13, 2026 06:49
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from 8756432 to 2957831 Compare September 13, 2026 07:15
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch 2 times, most recently from 8eb1b56 to dc78d28 Compare September 13, 2026 15:29
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from dc78d28 to e98f895 Compare September 13, 2026 15:40
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from e98f895 to ba40a77 Compare September 13, 2026 16:21
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from ba40a77 to d7361d3 Compare September 13, 2026 16:38
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from d7361d3 to 3540cb7 Compare September 13, 2026 18:29
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from 3540cb7 to 89af7b2 Compare September 13, 2026 18:53
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from 89af7b2 to a1e652a Compare September 13, 2026 19:09
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from a1e652a to 838af4b Compare September 13, 2026 19:49
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from 83f15bb to 5cb0364 Compare September 15, 2026 06:37

@syhuang22 syhuang22 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried this on v7x-8 too (same WAN 2.2 720p/81f/40 steps setup as my #478 comment):

  • The new default (per_q_block with U=4 on CP=4) is really R=1, so there's no ring. It comes in at 115.8s vs the current default's 105.3s (+10%). Can we keep the v7 default and add the v6e recipe as a separate option?
  • The -1.09% / bit-identical numbers look like v6e R=1 only. On v7 the output differs from main (the fused norm in #478), so mind scoping the description?
  • The description mentions dual-transformer warmup, but I don't see warmup changes in this diff, and main already primes both transformers since #452. Am I missing something?

# ulysses_custom_fixed_m: 3.5338 s/step
if [ -z "${ATTENTION:-}" ]; then
if [ "${FIXEDM:-1}" = "1" ]; then
ATTENTION="ulysses_ring_custom_fixed_m_per_q_block"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On v7 this default is ~10% slower than what we have now (115.8s vs 105.3s), and U=4 on CP=4 means R=1, so no ring at all. Could this be a v6e-only branch?

@Perseus14 Perseus14 Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 09866a3e (run_wan_fast_inference.sh:L171-L200)! The script now branches on TPU_PROFILE:

  • v7 defaults to ulysses_ring_custom_fixed_m with U=2 (R=2 on CP=4), BQ=6400, BKV=2048 (achieving 114.8s E2E on v7x-8; 101.4s Denoise and 102.1s E2E with p_state=7).
  • v6e defaults to ulysses_custom_fixed_m_per_q_block with U=4 (R=1 on CP=4), BQ=9472, BKV=1024 (achieving 138.7s E2E on v6e-8).

export LIBTPU_INIT_ARGS="${LIBTPU_INIT_ARGS} ${EXTRA_LIBTPU:-}"
# Tuned collective/scheduler flag set for TPU.
# One line: libtpu stops parsing at a literal backslash.
DEFAULT_LIBTPU="--xla_tpu_spmd_rng_bit_generator_unsafe=true --xla_tpu_enable_dot_strength_reduction=true --xla_enable_async_collective_permute=true --xla_tpu_enable_data_parallel_all_reduce_opt=true --xla_tpu_data_parallel_opt_different_sized_ops=true --xla_tpu_overlap_compute_collective_tc=true --xla_enable_async_all_gather=true --xla_tpu_scoped_vmem_limit_kib=65536 --xla_tpu_enable_async_all_to_all=true --xla_tpu_enable_all_experimental_scheduler_features=true --xla_tpu_enable_scheduler_memory_pressure_tracking=true --xla_tpu_host_transfer_overlap_limit=24 --xla_tpu_aggressive_opt_barrier_removal=ENABLED --xla_lhs_prioritize_async_depth_over_stall=ENABLED --xla_should_allow_loop_variant_parameter_in_chain=ENABLED --xla_should_add_loop_invariant_op_in_chain=ENABLED --xla_tpu_enable_ici_ag_pipelining=true --xla_max_concurrent_host_send_recv=100 --xla_tpu_scheduler_percent_shared_memory_limit=100 --xla_latency_hiding_scheduler_rerun=2 --xla_tpu_use_minor_sharding_for_major_trivial_input=true --xla_tpu_relayout_group_size_threshold_for_reduce_scatter=1 --xla_tpu_enable_latency_hiding_scheduler=true --xla_tpu_enable_ag_backward_pipelining=true --xla_tpu_enable_megacore_fusion=true --xla_tpu_megacore_fusion_allow_ags=true --xla_tpu_use_single_sparse_core_for_all_gather_offload=true --xla_tpu_sparse_core_all_gather_latency_multiplier=1 --xla_tpu_sparse_core_reduce_scatter_latency_multiplier=3 --xla_tpu_enable_sparse_core_collective_aggregator=true --xla_tpu_enable_sparse_core_offload_queuing_in_lhs=true --xla_tpu_enable_sparse_core_reduce_scatter_v2=true --xla_tpu_enable_sparse_core_collective_offload_all_gather=true --xla_tpu_enable_sparse_core_collective_offload_2d_all_gather=true --xla_tpu_enable_sparse_core_collective_offload_all_reduce=true --xla_tpu_enable_sparse_core_collective_offload_reduce_scatter=true --xla_tpu_enable_sparse_core_collective_offload_3d_all_gather=true --xla_tpu_enable_concurrent_sparse_core_offloading=true --xla_tpu_assign_all_reduce_scatter_layout=true"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three v7 flags got dropped here (async_collective_fusion, ..._fuse_all_gather, ..._multiple_steps). Intentional? Our v7 numbers were measured with them on.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Restored in 09866a3e (run_wan_fast_inference.sh:L136)! On v6e (Ghostlite), --xla_tpu_enable_async_collective_fusion=true aborts backend initialization (FAILED_PRECONDITION: Continuation fusion for AllGather is enabled on platform ghostlite), which is why it was disabled for v6e. We split V6E_LIBTPU and V7_LIBTPU so v7 explicitly enables all three flags (--xla_tpu_enable_async_collective_fusion=true --xla_tpu_enable_async_collective_fusion_fuse_all_gather=true --xla_tpu_enable_async_collective_fusion_multiple_steps=true) plus megacore fusion.

# fast serving recipe (persistent AOT + relayout fusion + unpadded KV): 136.5s (3.4125 s/step)
# baseline denoise (unfused, default recipe): 138.0s (3.4500 s/step)
# unoptimized baseline (cold/AOT disabled): 139.8s (3.4950 s/step)
# Bit-identical output (MD5: 4c18d4ce340e0da3a1d4c617926ad525) applies to the tested 40-step generation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This md5 only holds against your own baseline. Against main on v7 the output differs (fused RMSNorm in #478).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With fused_rmsnorm_rope updated in #478 to match Flax's x * (rsqrt * scale) order, online attention is bit-identical to main on v7 again. Also updated the comments here (L152-L165) to explicitly document that fixed-m attention reorders float accumulation (giving +3.25 dB PSNR vs exact online softmax and VBench within 0.25%) rather than claiming bit-identical md5 against online softmax.

detected_revision = commit_hash if commit_hash is not None else max_utils.get_git_commit_hash(check_dirty=True)
source_revision = _resolve_wan_aot_source_revision(config, detected_revision)
aot_cache_dir = getattr(config, "aot_cache_dir", "")
if aot_cache_dir and not _is_reusable_aot_revision(source_revision):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any untracked file makes the tree dirty and turns persistent AOT off, and keying on the commit throws the cache away on every commit, even docs-only ones. Maybe hash the relevant source files instead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestion — implemented both improvements:

  1. Source content hashing (generate_wan.py:L47-L93): Added _compute_wan_source_hash(), which hashes the sorted .py files that actually affect Wan's lowered HLO (aot_cache.py, models/attention_flax.py, models/wan/, pipelines/wan/, and kernels/ — 32 files, takes ~8.8 ms). Persistent AOT cache keys on src:<sha256[:16]> by default (unless overridden by aot_build_revision), so docs-only commits, changes to other models (flux, sdxl, ltx), or local edits to unrelated files never invalidate the AOT cache.
  2. Untracked files ignored (max_utils.py:L478): Passed --untracked-files=no to git status --porcelain in get_git_commit_hash(check_dirty=True) so untracked logs, videos, or scratch scripts never mark the working tree dirty.

return compiled(leaves)
except Exception as e: # noqa: BLE001 - any failure means "use jit"
max_logging.log(f"[aot] {self.name}: compiled call failed ({e}); using jit")
max_logging.log(f"[aot] fast-path execution failed for {self.name}/{signature} ({e}); falling back to JIT")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With _align_inputs inside this try, a sharding mismatch now quietly falls back to JIT (a recompile mid-serving) with just a log line. I'd keep align errors loud.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved flat = self._align_inputs(compiled, leaves) outside the try block (aot_cache.py:L236) and removed the blanket try/except inside _align_inputs (L259). Structural input pruning by XLA (len(flat_expected) != len(leaves)) still returns None cleanly, while any sharding mismatch or device_put error now raises loudly.

@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from 5cb0364 to 09e9a4a Compare September 16, 2026 04:45
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch 2 times, most recently from 526a9b5 to 905362a Compare September 16, 2026 07:04
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch 3 times, most recently from 09866a3 to 8631d1f Compare September 17, 2026 11:56
@Perseus14

Copy link
Copy Markdown
Collaborator Author

Thanks for testing PR #479 on v7x-8 @syhuang22! Updated the code and addressed your comments!

@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from 8631d1f to a909245 Compare September 17, 2026 12:31
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch 2 times, most recently from 3a0c913 to a415017 Compare September 17, 2026 14:34
syhuang22
syhuang22 previously approved these changes Sep 17, 2026
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from a415017 to 6b85dfc Compare September 17, 2026 18:41
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from 6b85dfc to 7e38c04 Compare September 17, 2026 18:51
@Perseus14
Perseus14 force-pushed the feat/wan-fast-serving branch from 7e38c04 to 1d65bfa Compare September 17, 2026 19:07
@Perseus14 Perseus14 self-assigned this Sep 17, 2026
@Perseus14
Perseus14 requested a review from eltsai September 17, 2026 19:09
…ce recipe

Delivers production-grade fast serving for Wan 2.2 T2V-A14B on Cloud TPU:
- Persistent AOT Compilation Caching with source hash and static graphdef metadata
- Tuned Serving Recipe: Production launcher with platform-specific v6e and v7 profiles
- Clean Key-Centering & 4-Operand Kernel (mk, q, k, v) without VMEM register pressure
- Merged Pre-A2A Norm Reduction (pmax) removing ring all_gather relayout barrier
- Hoisted scalar-prefetch metadata in _lse_scan eliminating collective-permute stalls
- Lossless 6D unpatchify transpose optimization (collapsing p_t=1 to avoid 8D stride copies)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants