Skip to content

Commit 4decf50

Browse files
committed
[perf] matrixgame2 causal: cache per-forward RoPE/timestep/blockmask recompute on-device
matrixgame2 causal inference rebuilt several constant tables on EVERY DiT forward, on CPU + H2D copy each time: - main RoPE tables (causal_model, float64 on CPU -> .to(device)) - sinusoidal timestep embedding (visual_embedding, arange on CPU -> .to) - action-module RoPE freqs (cached but as CPU tensors -> .to every call) - 4x flex-attention BlockMasks via create_block_mask (compile + a GPU-tensor python loop) every forward All of these depend only on constants (grid, start_frame, block params) that are fixed within an AR block (and mostly across the whole gen), so they are now memoized on-device keyed by their varying args. Numerically identical to before (pure memoization of deterministic functions); the sinusoid table is simply built on the target device instead of CPU+H2D. Measured (Modal H100x1, num_frames=117 = 10 AR blocks x 3 DMD steps, under nsys, vs the prior .item() fix): isolated DiT forward (microbench): 194 ms -> 95 ms (~2.04x) end-to-end inference wall: 17.2 s -> 13.1 s (-24%) denoising stage wall: 10.56 s -> 7.16 s (-32%) GPU active share (denoise): 23.0% -> 33.5% The biggest single contributor is the BlockMask cache (create_block_mask is expensive). Remaining GPU idle is now dominated by per-kernel Python dispatch of the ~150k small kernels, which only CUDA Graph / persistent kernel can address.
1 parent d6119c1 commit 4decf50

3 files changed

Lines changed: 81 additions & 46 deletions

File tree

fastvideo/layers/visual_embedding.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,11 @@ def timestep_embedding(t: torch.Tensor,
150150
Tensor of shape [B, dim] with embeddings
151151
"""
152152
half = dim // 2
153-
freqs = torch.exp(-math.log(max_period) * torch.arange(start=0, end=half, dtype=dtype) / half).to(device=t.device)
153+
# Build the frequency table directly on the target device. Creating it on
154+
# CPU and copying H2D every call is wasteful and breaks CUDA-graph capture
155+
# (H2D copy is illegal mid-capture).
156+
freqs = torch.exp(-math.log(max_period) *
157+
torch.arange(start=0, end=half, dtype=dtype, device=t.device) / half)
154158
args = t[:, None].float() * freqs[None]
155159
embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
156160
if dim % 2:

fastvideo/models/dits/matrixgame2/action_module.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -771,16 +771,21 @@ def forward(
771771
) % self.vae_time_compression_ratio == 0
772772
N_feats = int((N_frames - 1) / self.vae_time_compression_ratio) + 1
773773

774-
# Lazy initialization of freqs on first forward pass
774+
# Lazy initialization of freqs on first forward pass. Cache on the
775+
# compute device so the per-call `.to(xq.device)` in _apply_rotary_emb_qk
776+
# is a no-op (avoids an H2D copy every forward, which also breaks
777+
# CUDA-graph capture).
775778
if self._freqs_cos is None or self._freqs_sin is None:
776-
self._freqs_cos, self._freqs_sin = self.get_rotary_pos_embed(
779+
_fc, _fs = self.get_rotary_pos_embed(
777780
7500,
778781
self.patch_size[1],
779782
self.patch_size[2],
780783
64,
781784
self.mouse_qk_dim_list,
782785
start_offset=0,
783786
)
787+
self._freqs_cos = _fc.to(x.device)
788+
self._freqs_sin = _fs.to(x.device)
784789

785790
# Defined freqs_cis early so it's available for both mouse and keyboard
786791
freqs_cis = (self._freqs_cos, self._freqs_sin)

fastvideo/models/dits/matrixgame2/causal_model.py

Lines changed: 69 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -977,21 +977,36 @@ def _forward_inference(
977977

978978
d = self.hidden_size // self.num_attention_heads
979979
rope_dim_list = [d - 4 * (d // 6), 2 * (d // 6), 2 * (d // 6)]
980-
freqs_cos, freqs_sin = get_rotary_pos_embed(
981-
(
982-
post_patch_num_frames * get_sp_world_size(),
983-
post_patch_height,
984-
post_patch_width,
985-
),
986-
self.hidden_size,
987-
self.num_attention_heads,
988-
rope_dim_list,
989-
dtype=torch.float32 if current_platform.is_mps() else torch.float64,
990-
rope_theta=10000,
991-
start_frame=start_frame,
992-
)
993-
freqs_cos = freqs_cos.to(hidden_states.device)
994-
freqs_sin = freqs_sin.to(hidden_states.device)
980+
# RoPE tables depend only on (grid, start_frame); both are constant
981+
# within an AR block's DMD steps. Recomputing them on CPU in float64
982+
# and copying H2D every forward is wasteful AND breaks CUDA-graph
983+
# capture (H2D copy is illegal mid-capture). Cache the device tensors
984+
# keyed by (start_frame, grid) so the H2D happens once per block.
985+
_rope_key = (start_frame, post_patch_num_frames, post_patch_height,
986+
post_patch_width)
987+
_rope_cache = getattr(self, "_rope_device_cache", None)
988+
if _rope_cache is None:
989+
_rope_cache = {}
990+
self._rope_device_cache = _rope_cache
991+
if _rope_key in _rope_cache:
992+
freqs_cos, freqs_sin = _rope_cache[_rope_key]
993+
else:
994+
freqs_cos, freqs_sin = get_rotary_pos_embed(
995+
(
996+
post_patch_num_frames * get_sp_world_size(),
997+
post_patch_height,
998+
post_patch_width,
999+
),
1000+
self.hidden_size,
1001+
self.num_attention_heads,
1002+
rope_dim_list,
1003+
dtype=torch.float32 if current_platform.is_mps() else torch.float64,
1004+
rope_theta=10000,
1005+
start_frame=start_frame,
1006+
)
1007+
freqs_cos = freqs_cos.to(hidden_states.device)
1008+
freqs_sin = freqs_sin.to(hidden_states.device)
1009+
_rope_cache[_rope_key] = (freqs_cos, freqs_sin)
9951010
freqs_cis = (freqs_cos, freqs_sin) if freqs_cos is not None else None
9961011

9971012
hidden_states = self.patch_embedding(hidden_states)
@@ -1030,36 +1045,47 @@ def _forward_inference(
10301045
else:
10311046
encoder_hidden_states = encoder_hidden_states_image
10321047

1033-
block_mask = self._prepare_blockwise_causal_attn_mask(
1034-
device=hidden_states.device,
1035-
num_frames=num_frames,
1036-
frame_seqlen=post_patch_height * post_patch_width,
1037-
num_frame_per_block=self.num_frame_per_block,
1038-
local_attn_size=self.local_attn_size,
1039-
)
1048+
# BlockMasks depend only on (num_frames, frame_seqlen, block size,
1049+
# local_attn_size) — all constant across forwards. Building them every
1050+
# forward via create_block_mask is expensive (compile + GPU-tensor
1051+
# python loop) AND breaks CUDA-graph capture. Cache per param-tuple.
1052+
bm_cache = getattr(self, "_block_mask_cache", None)
1053+
if bm_cache is None:
1054+
bm_cache = {}
1055+
self._block_mask_cache = bm_cache
1056+
_dev = hidden_states.device
1057+
_fsl = post_patch_height * post_patch_width
1058+
_nfb = self.num_frame_per_block
1059+
_las = self.local_attn_size
1060+
1061+
_k = ("main", num_frames, _fsl, _nfb, _las)
1062+
if _k not in bm_cache:
1063+
bm_cache[_k] = self._prepare_blockwise_causal_attn_mask(
1064+
device=_dev, num_frames=num_frames, frame_seqlen=_fsl,
1065+
num_frame_per_block=_nfb, local_attn_size=_las)
1066+
block_mask = bm_cache[_k]
1067+
10401068
if self.use_rope_keyboard:
1041-
block_mask_keyboard = self._prepare_blockwise_causal_attn_mask_action(
1042-
device=hidden_states.device,
1043-
num_frames=num_frames,
1044-
frame_seqlen=1,
1045-
num_frame_per_block=self.num_frame_per_block,
1046-
local_attn_size=self.local_attn_size,
1047-
)
1069+
_k = ("act_kb", num_frames, 1, _nfb, _las)
1070+
if _k not in bm_cache:
1071+
bm_cache[_k] = self._prepare_blockwise_causal_attn_mask_action(
1072+
device=_dev, num_frames=num_frames, frame_seqlen=1,
1073+
num_frame_per_block=_nfb, local_attn_size=_las)
1074+
block_mask_keyboard = bm_cache[_k]
10481075
else:
1049-
block_mask_keyboard = self._prepare_blockwise_causal_attn_mask_keyboard(
1050-
device=hidden_states.device,
1051-
num_frames=num_frames,
1052-
frame_seqlen=post_patch_height * post_patch_width,
1053-
num_frame_per_block=self.num_frame_per_block,
1054-
local_attn_size=self.local_attn_size,
1055-
)
1056-
block_mask_mouse = self._prepare_blockwise_causal_attn_mask_action(
1057-
device=hidden_states.device,
1058-
num_frames=num_frames,
1059-
frame_seqlen=1,
1060-
num_frame_per_block=self.num_frame_per_block,
1061-
local_attn_size=self.local_attn_size,
1062-
)
1076+
_k = ("kb", num_frames, _fsl, _nfb, _las)
1077+
if _k not in bm_cache:
1078+
bm_cache[_k] = self._prepare_blockwise_causal_attn_mask_keyboard(
1079+
device=_dev, num_frames=num_frames, frame_seqlen=_fsl,
1080+
num_frame_per_block=_nfb, local_attn_size=_las)
1081+
block_mask_keyboard = bm_cache[_k]
1082+
1083+
_k = ("act_ms", num_frames, 1, _nfb, _las)
1084+
if _k not in bm_cache:
1085+
bm_cache[_k] = self._prepare_blockwise_causal_attn_mask_action(
1086+
device=_dev, num_frames=num_frames, frame_seqlen=1,
1087+
num_frame_per_block=_nfb, local_attn_size=_las)
1088+
block_mask_mouse = bm_cache[_k]
10631089
if kv_cache is None:
10641090
kv_cache = [None] * len(self.blocks)
10651091
if kv_cache_mouse is None:

0 commit comments

Comments
 (0)