Add DeMemWM segment-aware temporal mask
Browse files
algorithms/dememwm/models/attention.py
CHANGED
|
@@ -44,7 +44,56 @@ class TemporalAxialAttention(nn.Module):
|
|
| 44 |
|
| 45 |
self.reference_length = reference_length
|
| 46 |
|
| 47 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
B, T, H, W, D = x.shape
|
| 49 |
|
| 50 |
# if T>=9:
|
|
@@ -75,7 +124,18 @@ class TemporalAxialAttention(nn.Module):
|
|
| 75 |
|
| 76 |
q, k, v = map(lambda t: t.contiguous(), (q, k, v))
|
| 77 |
|
| 78 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
attn_bias = torch.ones((T, T), dtype=q.dtype, device=q.device)
|
| 80 |
attn_bias = attn_bias.masked_fill(attn_bias == 1, float('-inf'))
|
| 81 |
attn_bias[range(T), range(T)] = 0
|
|
|
|
| 44 |
|
| 45 |
self.reference_length = reference_length
|
| 46 |
|
| 47 |
+
def _frame_memory_attn_bias(
|
| 48 |
+
self,
|
| 49 |
+
B,
|
| 50 |
+
T,
|
| 51 |
+
H,
|
| 52 |
+
W,
|
| 53 |
+
dtype,
|
| 54 |
+
device,
|
| 55 |
+
frame_memory_segments,
|
| 56 |
+
frame_memory_masks,
|
| 57 |
+
):
|
| 58 |
+
allow = torch.zeros((B, T, T), dtype=torch.bool, device=device)
|
| 59 |
+
|
| 60 |
+
target_frames = frame_memory_segments["target"] if frame_memory_segments else T
|
| 61 |
+
cursor = 0
|
| 62 |
+
target_slice = slice(cursor, cursor + target_frames)
|
| 63 |
+
target_idx = torch.arange(target_frames, device=device)
|
| 64 |
+
allow[:, target_slice, target_slice] = target_idx[:, None] >= target_idx[None, :]
|
| 65 |
+
cursor += target_frames
|
| 66 |
+
|
| 67 |
+
segment_slices = {"target": target_slice}
|
| 68 |
+
for segment in ("anchor", "dynamic", "revisit"):
|
| 69 |
+
length = frame_memory_segments.get(segment, 0)
|
| 70 |
+
segment_slice = slice(cursor, cursor + length)
|
| 71 |
+
segment_slices[segment] = segment_slice
|
| 72 |
+
if length > 0:
|
| 73 |
+
if segment == "dynamic":
|
| 74 |
+
allow[:, segment_slice, segment_slice] = True
|
| 75 |
+
else:
|
| 76 |
+
idx = torch.arange(cursor, cursor + length, device=device)
|
| 77 |
+
allow[:, idx, idx] = True
|
| 78 |
+
cursor += length
|
| 79 |
+
|
| 80 |
+
if frame_memory_masks is not None:
|
| 81 |
+
valid = torch.ones((B, T), dtype=torch.bool, device=device)
|
| 82 |
+
for segment, segment_slice in segment_slices.items():
|
| 83 |
+
mask = frame_memory_masks.get(segment)
|
| 84 |
+
if mask is not None:
|
| 85 |
+
valid[:, segment_slice] = mask.to(device=device, dtype=torch.bool)
|
| 86 |
+
allow = allow & valid[:, :, None] & valid[:, None, :]
|
| 87 |
+
|
| 88 |
+
# Invalid padded rows keep only self-attention finite so SDPA never sees
|
| 89 |
+
# an all -inf query row.
|
| 90 |
+
diag_idx = torch.arange(T, device=device)
|
| 91 |
+
allow[:, diag_idx, diag_idx] = True
|
| 92 |
+
attn_bias = torch.zeros((B, T, T), dtype=dtype, device=device)
|
| 93 |
+
attn_bias = attn_bias.masked_fill(~allow, float("-inf"))
|
| 94 |
+
return attn_bias.repeat_interleave(H * W, dim=0)[:, None]
|
| 95 |
+
|
| 96 |
+
def forward(self, x: torch.Tensor, frame_memory_segments=None, frame_memory_masks=None):
|
| 97 |
B, T, H, W, D = x.shape
|
| 98 |
|
| 99 |
# if T>=9:
|
|
|
|
| 124 |
|
| 125 |
q, k, v = map(lambda t: t.contiguous(), (q, k, v))
|
| 126 |
|
| 127 |
+
if frame_memory_segments is not None:
|
| 128 |
+
attn_bias = self._frame_memory_attn_bias(
|
| 129 |
+
B,
|
| 130 |
+
T,
|
| 131 |
+
H,
|
| 132 |
+
W,
|
| 133 |
+
q.dtype,
|
| 134 |
+
q.device,
|
| 135 |
+
frame_memory_segments,
|
| 136 |
+
frame_memory_masks,
|
| 137 |
+
)
|
| 138 |
+
elif self.is_temporal_independent:
|
| 139 |
attn_bias = torch.ones((T, T), dtype=q.dtype, device=q.device)
|
| 140 |
attn_bias = attn_bias.masked_fill(attn_bias == 1, float('-inf'))
|
| 141 |
attn_bias[range(T), range(T)] = 0
|
algorithms/dememwm/models/dit.py
CHANGED
|
@@ -241,7 +241,8 @@ class SpatioTemporalDiTBlock(nn.Module):
|
|
| 241 |
self.parallel_map = nn.Linear(hidden_size, hidden_size)
|
| 242 |
|
| 243 |
def forward(self, x, c, current_frame=None, timestep=None, is_last_block=False,
|
| 244 |
-
pose_cond=None, mode="training", c_action_cond=None, reference_length=None
|
|
|
|
| 245 |
B, T, H, W, D = x.shape
|
| 246 |
|
| 247 |
# spatial block
|
|
@@ -256,7 +257,11 @@ class SpatioTemporalDiTBlock(nn.Module):
|
|
| 256 |
else:
|
| 257 |
t_shift_msa, t_scale_msa, t_gate_msa, t_shift_mlp, t_scale_mlp, t_gate_mlp = self.t_adaLN_modulation(c).chunk(6, dim=-1)
|
| 258 |
|
| 259 |
-
x_t = x + gate(self.t_attn(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
x_t = x_t + gate(self.t_mlp(modulate(self.t_norm2(x_t), t_shift_mlp, t_scale_mlp)), t_gate_mlp)
|
| 261 |
|
| 262 |
if self.ref_mode == 'sequential':
|
|
@@ -540,7 +545,9 @@ class DiT(nn.Module):
|
|
| 540 |
|
| 541 |
for i, block in enumerate(self.blocks):
|
| 542 |
x = block(x, c, current_frame=current_frame, timestep=t, is_last_block= (i+1 == len(self.blocks)),
|
| 543 |
-
pose_cond=pc, mode=mode, c_action_cond=c_action_cond, reference_length=reference_length
|
|
|
|
|
|
|
| 544 |
x = self.final_layer(x, c) # (N, T, H, W, patch_size ** 2 * out_channels)
|
| 545 |
# unpatchify
|
| 546 |
x = rearrange(x, "b t h w d -> (b t) h w d")
|
|
|
|
| 241 |
self.parallel_map = nn.Linear(hidden_size, hidden_size)
|
| 242 |
|
| 243 |
def forward(self, x, c, current_frame=None, timestep=None, is_last_block=False,
|
| 244 |
+
pose_cond=None, mode="training", c_action_cond=None, reference_length=None,
|
| 245 |
+
frame_memory_segments=None, frame_memory_masks=None):
|
| 246 |
B, T, H, W, D = x.shape
|
| 247 |
|
| 248 |
# spatial block
|
|
|
|
| 257 |
else:
|
| 258 |
t_shift_msa, t_scale_msa, t_gate_msa, t_shift_mlp, t_scale_mlp, t_gate_mlp = self.t_adaLN_modulation(c).chunk(6, dim=-1)
|
| 259 |
|
| 260 |
+
x_t = x + gate(self.t_attn(
|
| 261 |
+
modulate(self.t_norm1(x), t_shift_msa, t_scale_msa),
|
| 262 |
+
frame_memory_segments=frame_memory_segments,
|
| 263 |
+
frame_memory_masks=frame_memory_masks,
|
| 264 |
+
), t_gate_msa)
|
| 265 |
x_t = x_t + gate(self.t_mlp(modulate(self.t_norm2(x_t), t_shift_mlp, t_scale_mlp)), t_gate_mlp)
|
| 266 |
|
| 267 |
if self.ref_mode == 'sequential':
|
|
|
|
| 545 |
|
| 546 |
for i, block in enumerate(self.blocks):
|
| 547 |
x = block(x, c, current_frame=current_frame, timestep=t, is_last_block= (i+1 == len(self.blocks)),
|
| 548 |
+
pose_cond=pc, mode=mode, c_action_cond=c_action_cond, reference_length=reference_length,
|
| 549 |
+
frame_memory_segments=frame_memory_segments,
|
| 550 |
+
frame_memory_masks=frame_memory_masks) # (N, T, H, W, D)
|
| 551 |
x = self.final_layer(x, c) # (N, T, H, W, patch_size ** 2 * out_channels)
|
| 552 |
# unpatchify
|
| 553 |
x = rearrange(x, "b t h w d -> (b t) h w d")
|
tests/test_dememwm_temporal_attention.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import unittest
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from torch import nn
|
| 5 |
+
|
| 6 |
+
from algorithms.dememwm.models.attention import TemporalAxialAttention
|
| 7 |
+
from algorithms.dememwm.models.dit import DiT, SpatioTemporalDiTBlock
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class IdentityRotary:
|
| 11 |
+
freqs = None
|
| 12 |
+
|
| 13 |
+
def rotate_queries_or_keys(self, x, freqs):
|
| 14 |
+
return x
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def _averaging_temporal_attention(reference_length=0):
|
| 18 |
+
attn = TemporalAxialAttention(
|
| 19 |
+
dim=1,
|
| 20 |
+
heads=1,
|
| 21 |
+
dim_head=1,
|
| 22 |
+
reference_length=reference_length,
|
| 23 |
+
rotary_emb=IdentityRotary(),
|
| 24 |
+
)
|
| 25 |
+
with torch.no_grad():
|
| 26 |
+
attn.to_qkv.weight.zero_()
|
| 27 |
+
attn.to_qkv.weight[2, 0] = 1.0
|
| 28 |
+
attn.to_out.weight.fill_(1.0)
|
| 29 |
+
attn.to_out.bias.zero_()
|
| 30 |
+
return attn
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class DeMemWMTemporalAttentionTests(unittest.TestCase):
|
| 34 |
+
def test_without_frame_memory_keeps_causal_reference_mask(self):
|
| 35 |
+
attn = _averaging_temporal_attention(reference_length=2)
|
| 36 |
+
x = torch.tensor([10.0, 20.0, 30.0, 100.0, 200.0]).view(1, 5, 1, 1, 1)
|
| 37 |
+
|
| 38 |
+
out = attn(x)
|
| 39 |
+
|
| 40 |
+
expected = torch.tensor([10.0, 15.0, 20.0, 100.0, 200.0])
|
| 41 |
+
self.assertTrue(torch.allclose(out.flatten(), expected))
|
| 42 |
+
|
| 43 |
+
def test_frame_memory_segments_mask_temporal_streams(self):
|
| 44 |
+
attn = _averaging_temporal_attention()
|
| 45 |
+
x = torch.tensor([10.0, 20.0, 30.0, 100.0, 1.0, 3.0, 200.0]).view(1, 7, 1, 1, 1)
|
| 46 |
+
segments = {"target": 3, "anchor": 1, "dynamic": 2, "revisit": 1}
|
| 47 |
+
|
| 48 |
+
out = attn(x, frame_memory_segments=segments)
|
| 49 |
+
|
| 50 |
+
expected = torch.tensor([10.0, 15.0, 20.0, 100.0, 2.0, 2.0, 200.0])
|
| 51 |
+
self.assertTrue(torch.allclose(out.flatten(), expected))
|
| 52 |
+
|
| 53 |
+
masks = {
|
| 54 |
+
"target": torch.ones((1, 3), dtype=torch.bool),
|
| 55 |
+
"anchor": torch.tensor([[False]]),
|
| 56 |
+
"dynamic": torch.tensor([[True, False]]),
|
| 57 |
+
"revisit": torch.ones((1, 1), dtype=torch.bool),
|
| 58 |
+
}
|
| 59 |
+
out = attn(x, frame_memory_segments=segments, frame_memory_masks=masks)
|
| 60 |
+
|
| 61 |
+
expected = torch.tensor([10.0, 15.0, 20.0, 100.0, 1.0, 3.0, 200.0])
|
| 62 |
+
self.assertTrue(torch.allclose(out.flatten(), expected))
|
| 63 |
+
|
| 64 |
+
def test_block_threads_frame_memory_metadata_to_temporal_attention(self):
|
| 65 |
+
class ZeroAttention(nn.Module):
|
| 66 |
+
def forward(self, x):
|
| 67 |
+
return torch.zeros_like(x)
|
| 68 |
+
|
| 69 |
+
class SpyTemporalAttention(nn.Module):
|
| 70 |
+
def __init__(self):
|
| 71 |
+
super().__init__()
|
| 72 |
+
self.calls = []
|
| 73 |
+
|
| 74 |
+
def forward(self, x, frame_memory_segments=None, frame_memory_masks=None):
|
| 75 |
+
self.calls.append((frame_memory_segments, frame_memory_masks))
|
| 76 |
+
return torch.zeros_like(x)
|
| 77 |
+
|
| 78 |
+
block = SpatioTemporalDiTBlock(
|
| 79 |
+
hidden_size=4,
|
| 80 |
+
num_heads=1,
|
| 81 |
+
reference_length=0,
|
| 82 |
+
spatial_rotary_emb=None,
|
| 83 |
+
temporal_rotary_emb=None,
|
| 84 |
+
)
|
| 85 |
+
spy = SpyTemporalAttention()
|
| 86 |
+
block.s_attn = ZeroAttention()
|
| 87 |
+
block.t_attn = spy
|
| 88 |
+
x = torch.zeros((1, 5, 1, 1, 4))
|
| 89 |
+
c = torch.zeros((1, 5, 4))
|
| 90 |
+
segments = {"target": 2, "anchor": 1, "dynamic": 1, "revisit": 1}
|
| 91 |
+
masks = {"dynamic": torch.ones((1, 1), dtype=torch.bool)}
|
| 92 |
+
|
| 93 |
+
block(x, c, frame_memory_segments=segments, frame_memory_masks=masks)
|
| 94 |
+
|
| 95 |
+
self.assertIs(spy.calls[0][0], segments)
|
| 96 |
+
self.assertIs(spy.calls[0][1], masks)
|
| 97 |
+
|
| 98 |
+
def test_dit_threads_frame_memory_metadata_to_blocks(self):
|
| 99 |
+
class SpyBlock(nn.Module):
|
| 100 |
+
def __init__(self):
|
| 101 |
+
super().__init__()
|
| 102 |
+
self.calls = []
|
| 103 |
+
|
| 104 |
+
def forward(self, x, c, **kwargs):
|
| 105 |
+
self.calls.append(kwargs)
|
| 106 |
+
return x
|
| 107 |
+
|
| 108 |
+
model = DiT(
|
| 109 |
+
input_h=2,
|
| 110 |
+
input_w=2,
|
| 111 |
+
patch_size=1,
|
| 112 |
+
in_channels=1,
|
| 113 |
+
hidden_size=8,
|
| 114 |
+
depth=1,
|
| 115 |
+
num_heads=1,
|
| 116 |
+
mlp_ratio=1.0,
|
| 117 |
+
action_cond_dim=3,
|
| 118 |
+
pose_cond_dim=0,
|
| 119 |
+
reference_length=0,
|
| 120 |
+
use_memory_attention=False,
|
| 121 |
+
)
|
| 122 |
+
spy = SpyBlock()
|
| 123 |
+
model.blocks[0] = spy
|
| 124 |
+
model.eval()
|
| 125 |
+
x = torch.zeros((1, 5, 1, 2, 2))
|
| 126 |
+
t = torch.zeros((1, 5), dtype=torch.long)
|
| 127 |
+
action_cond = torch.zeros((1, 5, 3))
|
| 128 |
+
segments = {"target": 2, "anchor": 1, "dynamic": 1, "revisit": 1}
|
| 129 |
+
masks = {"dynamic": torch.ones((1, 1), dtype=torch.bool)}
|
| 130 |
+
|
| 131 |
+
with torch.no_grad():
|
| 132 |
+
model(x, t, action_cond, frame_memory_segments=segments, frame_memory_masks=masks)
|
| 133 |
+
|
| 134 |
+
self.assertIs(spy.calls[0]["frame_memory_segments"], segments)
|
| 135 |
+
self.assertIs(spy.calls[0]["frame_memory_masks"], masks)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
if __name__ == "__main__":
|
| 139 |
+
unittest.main()
|