Skip to content

Commit ffa3136

Browse files
committed
[perf]: cache Wan DiT rotary embeddings per (shape, device)
Wan's DiT forward rebuilt rotary embeddings on every call via a float64 CPU compute (`get_rotary_pos_embed`) + H2D copy of the cos/sin pair. The inputs — post-patch shape + compute device — are constant across every step of a single generation, so the precompute happens once per (shape, device) and is reused for the rest of the denoise loop (180+ forwards for a 50-step CFG run). Mirrors the matrixgame2 fix in hao-ai-lab#1415: same hotspot, different model, same cache-on-self pattern. Composes with hao-ai-lab#1245's fused RoPE Triton kernel (that PR optimizes each `_apply_rotary_emb` call site; this one removes the recurring per-forward precompute upstream of it). Math is identical — first call computes, subsequent calls memoize. Bit-exact equivalence with the prior path expected.
1 parent 5706079 commit ffa3136

1 file changed

Lines changed: 30 additions & 13 deletions

File tree

fastvideo/models/dits/wanvideo.py

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -636,19 +636,36 @@ def forward(self,
636636
post_patch_height = height // p_h
637637
post_patch_width = width // p_w
638638

639-
# Get rotary embeddings
640-
d = self.hidden_size // self.num_attention_heads
641-
rope_dim_list = [d - 4 * (d // 6), 2 * (d // 6), 2 * (d // 6)]
642-
freqs_cos, freqs_sin = get_rotary_pos_embed(
643-
(post_patch_num_frames, post_patch_height,
644-
post_patch_width),
645-
self.hidden_size,
646-
self.num_attention_heads,
647-
rope_dim_list,
648-
dtype=torch.float32 if current_platform.is_mps() else torch.float64,
649-
rope_theta=10000)
650-
freqs_cis = (freqs_cos.to(hidden_states.device).float(),
651-
freqs_sin.to(hidden_states.device).float())
639+
# Rotary embeddings depend only on the post-patch shape and the
640+
# compute device — both constant across every step of a single
641+
# generation. The original code rebuilt them on every forward via a
642+
# float64 CPU compute + H2D copy (180+ times for a 50-step CFG run).
643+
# Cache per (shape, device) tuple; cache miss only on the first
644+
# forward of a new shape (or after a device change). Same pattern as
645+
# matrixgame2 PR #1415 — different model, same recompute hotspot.
646+
# Bit-exact with the original: math is identical, just memoized.
647+
rope_cache = getattr(self, "_rope_cache", None)
648+
if rope_cache is None:
649+
rope_cache = {}
650+
self._rope_cache = rope_cache
651+
rope_key = (post_patch_num_frames, post_patch_height,
652+
post_patch_width, hidden_states.device)
653+
if rope_key not in rope_cache:
654+
d = self.hidden_size // self.num_attention_heads
655+
rope_dim_list = [d - 4 * (d // 6), 2 * (d // 6), 2 * (d // 6)]
656+
freqs_cos, freqs_sin = get_rotary_pos_embed(
657+
(post_patch_num_frames, post_patch_height,
658+
post_patch_width),
659+
self.hidden_size,
660+
self.num_attention_heads,
661+
rope_dim_list,
662+
dtype=torch.float32 if current_platform.is_mps() else torch.float64,
663+
rope_theta=10000)
664+
rope_cache[rope_key] = (
665+
freqs_cos.to(hidden_states.device).float(),
666+
freqs_sin.to(hidden_states.device).float(),
667+
)
668+
freqs_cis = rope_cache[rope_key]
652669

653670
hidden_states = self.patch_embedding(hidden_states)
654671
hidden_states = hidden_states.flatten(2).transpose(1, 2)

0 commit comments

Comments
 (0)