Skip to content

Commit ac84d6f

Browse files
committed
[perf] matrixgame2 causal: cut per-forward recompute (blockmask cache, skip unused RoPE, on-device timestep)
matrixgame2 causal inference repeated constant work on every DiT forward: - 4 flex-attention BlockMasks were rebuilt via create_block_mask each forward (compile + a GPU-tensor python loop). They depend only on (num_frames, frame_seqlen, block size, local_attn_size, device), so cache them per param-tuple (device included in the key). - get_rotary_pos_embed ran a float64 CPU compute + H2D copy each forward to build freqs_cis — which CausalMatrixGame2SelfAttention.forward never reads (it computes its own RoPE via self._freqs_cache). Set freqs_cis = None and skip the computation entirely. - the sinusoidal timestep table was built on CPU then copied H2D; build it on the target device instead. - the action-module RoPE freqs were cached as CPU tensors, forcing an H2D copy every forward inside _apply_rotary_emb_qk; cache them device-resident (with a device check) so the per-call .to is a no-op. All numerically identical. Removes per-forward CPU compute + H2D copies that also blocked CUDA-graph capture. Measured (H100, 117 frames = 30 DiT forwards): isolated DiT forward 194 ms -> 95 ms; end-to-end inference 12.6 s -> 10.6 s (-16%); denoise-stage GPU-active 23% -> 33.5%.
1 parent d6119c1 commit ac84d6f

3 files changed

Lines changed: 60 additions & 50 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: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -771,16 +771,22 @@ 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
775-
if self._freqs_cos is None or self._freqs_sin is None:
776-
self._freqs_cos, self._freqs_sin = self.get_rotary_pos_embed(
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).
778+
if (self._freqs_cos is None or self._freqs_sin is None
779+
or self._freqs_cos.device != x.device):
780+
_fc, _fs = self.get_rotary_pos_embed(
777781
7500,
778782
self.patch_size[1],
779783
self.patch_size[2],
780784
64,
781785
self.mouse_qk_dim_list,
782786
start_offset=0,
783787
)
788+
self._freqs_cos = _fc.to(x.device)
789+
self._freqs_sin = _fs.to(x.device)
784790

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

fastvideo/models/dits/matrixgame2/causal_model.py

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -975,24 +975,13 @@ def _forward_inference(
975975
post_patch_height = height // p_h
976976
post_patch_width = width // p_w
977977

978-
d = self.hidden_size // self.num_attention_heads
979-
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)
995-
freqs_cis = (freqs_cos, freqs_sin) if freqs_cos is not None else None
978+
# freqs_cis is unused by CausalMatrixGame2SelfAttention.forward — the
979+
# attention computes its own RoPE via self._freqs_cache + causal_rope_apply
980+
# and ignores the freqs_cis argument. So skip get_rotary_pos_embed
981+
# entirely: it was running a float64 CPU compute + H2D copy on every
982+
# forward (also illegal during CUDA-graph capture) for a result nothing
983+
# reads.
984+
freqs_cis = None
996985

997986
hidden_states = self.patch_embedding(hidden_states)
998987
grid_sizes = (post_patch_num_frames, post_patch_height, post_patch_width)
@@ -1030,36 +1019,47 @@ def _forward_inference(
10301019
else:
10311020
encoder_hidden_states = encoder_hidden_states_image
10321021

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-
)
1022+
# BlockMasks depend only on (num_frames, frame_seqlen, block size,
1023+
# local_attn_size) — all constant across forwards. Building them every
1024+
# forward via create_block_mask is expensive (compile + GPU-tensor
1025+
# python loop) AND breaks CUDA-graph capture. Cache per param-tuple.
1026+
bm_cache = getattr(self, "_block_mask_cache", None)
1027+
if bm_cache is None:
1028+
bm_cache = {}
1029+
self._block_mask_cache = bm_cache
1030+
_dev = hidden_states.device
1031+
_fsl = post_patch_height * post_patch_width
1032+
_nfb = self.num_frame_per_block
1033+
_las = self.local_attn_size
1034+
1035+
_k = ("main", num_frames, _fsl, _nfb, _las, _dev)
1036+
if _k not in bm_cache:
1037+
bm_cache[_k] = self._prepare_blockwise_causal_attn_mask(
1038+
device=_dev, num_frames=num_frames, frame_seqlen=_fsl,
1039+
num_frame_per_block=_nfb, local_attn_size=_las)
1040+
block_mask = bm_cache[_k]
1041+
10401042
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-
)
1043+
_k = ("act_kb", num_frames, 1, _nfb, _las, _dev)
1044+
if _k not in bm_cache:
1045+
bm_cache[_k] = self._prepare_blockwise_causal_attn_mask_action(
1046+
device=_dev, num_frames=num_frames, frame_seqlen=1,
1047+
num_frame_per_block=_nfb, local_attn_size=_las)
1048+
block_mask_keyboard = bm_cache[_k]
10481049
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-
)
1050+
_k = ("kb", num_frames, _fsl, _nfb, _las, _dev)
1051+
if _k not in bm_cache:
1052+
bm_cache[_k] = self._prepare_blockwise_causal_attn_mask_keyboard(
1053+
device=_dev, num_frames=num_frames, frame_seqlen=_fsl,
1054+
num_frame_per_block=_nfb, local_attn_size=_las)
1055+
block_mask_keyboard = bm_cache[_k]
1056+
1057+
_k = ("act_ms", num_frames, 1, _nfb, _las, _dev)
1058+
if _k not in bm_cache:
1059+
bm_cache[_k] = self._prepare_blockwise_causal_attn_mask_action(
1060+
device=_dev, num_frames=num_frames, frame_seqlen=1,
1061+
num_frame_per_block=_nfb, local_attn_size=_las)
1062+
block_mask_mouse = bm_cache[_k]
10631063
if kv_cache is None:
10641064
kv_cache = [None] * len(self.blocks)
10651065
if kv_cache_mouse is None:

0 commit comments

Comments
 (0)