Build DeMemWM frame memory geometry cache
Browse files
algorithms/dememwm/models/dit.py
CHANGED
|
@@ -155,7 +155,7 @@ class FrameMemoryReferenceAttention(nn.Module):
|
|
| 155 |
def _split_heads(self, x):
|
| 156 |
return rearrange(x, "b n (h d) -> b h n d", h=self.num_heads)
|
| 157 |
|
| 158 |
-
def forward(self, target_hidden, memory_hidden, memory_mask=None):
|
| 159 |
B, T_target, H, W, D = target_hidden.shape
|
| 160 |
if memory_hidden is None or int(memory_hidden.shape[1]) == 0:
|
| 161 |
return target_hidden.new_zeros(target_hidden.shape)
|
|
@@ -321,7 +321,7 @@ class SpatioTemporalDiTBlock(nn.Module):
|
|
| 321 |
return None
|
| 322 |
return frame_memory_masks[stream_name].to(device=stream_hidden.device, dtype=torch.bool)
|
| 323 |
|
| 324 |
-
def _apply_frame_memory_reference_attention(self, x, c, frame_memory_segments, frame_memory_masks):
|
| 325 |
x_target, x_anchor, x_dynamic, x_revisit = self._split_frame_memory(x, frame_memory_segments)
|
| 326 |
if int(x_target.shape[1]) == 0:
|
| 327 |
return x
|
|
@@ -340,7 +340,8 @@ class SpatioTemporalDiTBlock(nn.Module):
|
|
| 340 |
if int(stream_hidden.shape[1]) == 0:
|
| 341 |
continue
|
| 342 |
stream_mask = self._frame_memory_stream_mask(frame_memory_masks, stream_name, stream_hidden)
|
| 343 |
-
|
|
|
|
| 344 |
if stream_mask is None:
|
| 345 |
active_stream_count = active_stream_count + 1
|
| 346 |
else:
|
|
@@ -362,7 +363,7 @@ class SpatioTemporalDiTBlock(nn.Module):
|
|
| 362 |
def forward(self, x, c, current_frame=None, timestep=None, is_last_block=False,
|
| 363 |
pose_cond=None, mode="training", c_action_cond=None, reference_length=None,
|
| 364 |
frame_memory_segments=None, frame_memory_masks=None, frame_memory_pose=None,
|
| 365 |
-
image_hw=None):
|
| 366 |
B, T, H, W, D = x.shape
|
| 367 |
|
| 368 |
# spatial block
|
|
@@ -396,6 +397,7 @@ class SpatioTemporalDiTBlock(nn.Module):
|
|
| 396 |
c,
|
| 397 |
frame_memory_segments,
|
| 398 |
frame_memory_masks,
|
|
|
|
| 399 |
)
|
| 400 |
elif self.use_memory_attention:
|
| 401 |
r_shift_msa, r_scale_msa, r_gate_msa, r_shift_mlp, r_scale_mlp, r_gate_mlp = self.r_adaLN_modulation(c).chunk(6, dim=-1)
|
|
@@ -611,6 +613,84 @@ class DiT(nn.Module):
|
|
| 611 |
imgs = x.reshape(shape=(x.shape[0], c, h * p, w * p))
|
| 612 |
return imgs
|
| 613 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
def forward(self, x, t, action_cond=None, pose_cond=None, current_frame=None, mode=None,
|
| 615 |
reference_length=None, frame_idx=None, frame_memory_segments=None,
|
| 616 |
frame_memory_masks=None, frame_memory_pose=None, image_hw=None):
|
|
@@ -670,13 +750,25 @@ class DiT(nn.Module):
|
|
| 670 |
else:
|
| 671 |
pc = None
|
| 672 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 673 |
for i, block in enumerate(self.blocks):
|
| 674 |
x = block(x, c, current_frame=current_frame, timestep=t, is_last_block= (i+1 == len(self.blocks)),
|
| 675 |
pose_cond=pc, mode=mode, c_action_cond=c_action_cond, reference_length=reference_length,
|
| 676 |
frame_memory_segments=frame_memory_segments,
|
| 677 |
frame_memory_masks=frame_memory_masks,
|
| 678 |
frame_memory_pose=frame_memory_pose,
|
| 679 |
-
image_hw=image_hw
|
|
|
|
| 680 |
x = self.final_layer(x, c) # (N, T, H, W, patch_size ** 2 * out_channels)
|
| 681 |
# unpatchify
|
| 682 |
x = rearrange(x, "b t h w d -> (b t) h w d")
|
|
|
|
| 155 |
def _split_heads(self, x):
|
| 156 |
return rearrange(x, "b n (h d) -> b h n d", h=self.num_heads)
|
| 157 |
|
| 158 |
+
def forward(self, target_hidden, memory_hidden, memory_mask=None, geometry_cache=None):
|
| 159 |
B, T_target, H, W, D = target_hidden.shape
|
| 160 |
if memory_hidden is None or int(memory_hidden.shape[1]) == 0:
|
| 161 |
return target_hidden.new_zeros(target_hidden.shape)
|
|
|
|
| 321 |
return None
|
| 322 |
return frame_memory_masks[stream_name].to(device=stream_hidden.device, dtype=torch.bool)
|
| 323 |
|
| 324 |
+
def _apply_frame_memory_reference_attention(self, x, c, frame_memory_segments, frame_memory_masks, frame_memory_geometry=None):
|
| 325 |
x_target, x_anchor, x_dynamic, x_revisit = self._split_frame_memory(x, frame_memory_segments)
|
| 326 |
if int(x_target.shape[1]) == 0:
|
| 327 |
return x
|
|
|
|
| 340 |
if int(stream_hidden.shape[1]) == 0:
|
| 341 |
continue
|
| 342 |
stream_mask = self._frame_memory_stream_mask(frame_memory_masks, stream_name, stream_hidden)
|
| 343 |
+
stream_geometry = None if frame_memory_geometry is None else frame_memory_geometry.get(stream_name)
|
| 344 |
+
deltas.append(r_attn(attn_target, stream_attn, stream_mask, geometry_cache=stream_geometry))
|
| 345 |
if stream_mask is None:
|
| 346 |
active_stream_count = active_stream_count + 1
|
| 347 |
else:
|
|
|
|
| 363 |
def forward(self, x, c, current_frame=None, timestep=None, is_last_block=False,
|
| 364 |
pose_cond=None, mode="training", c_action_cond=None, reference_length=None,
|
| 365 |
frame_memory_segments=None, frame_memory_masks=None, frame_memory_pose=None,
|
| 366 |
+
image_hw=None, frame_memory_geometry=None):
|
| 367 |
B, T, H, W, D = x.shape
|
| 368 |
|
| 369 |
# spatial block
|
|
|
|
| 397 |
c,
|
| 398 |
frame_memory_segments,
|
| 399 |
frame_memory_masks,
|
| 400 |
+
frame_memory_geometry,
|
| 401 |
)
|
| 402 |
elif self.use_memory_attention:
|
| 403 |
r_shift_msa, r_scale_msa, r_gate_msa, r_shift_mlp, r_scale_mlp, r_gate_mlp = self.r_adaLN_modulation(c).chunk(6, dim=-1)
|
|
|
|
| 613 |
imgs = x.reshape(shape=(x.shape[0], c, h * p, w * p))
|
| 614 |
return imgs
|
| 615 |
|
| 616 |
+
def _frame_memory_pose_to_c2w(self, pose):
|
| 617 |
+
B, T = pose.shape[:2]
|
| 618 |
+
x, y, z, pitch, yaw = pose.unbind(dim=-1)
|
| 619 |
+
pitch, yaw = torch.deg2rad(pitch), torch.deg2rad(yaw)
|
| 620 |
+
cp, sp = torch.cos(pitch), torch.sin(pitch)
|
| 621 |
+
cy, sy = torch.cos(yaw), torch.sin(yaw)
|
| 622 |
+
one, zero = torch.ones_like(pitch), torch.zeros_like(pitch)
|
| 623 |
+
r_pitch = torch.stack((one, zero, zero, zero, cp, -sp, zero, sp, cp), dim=-1).reshape(B, T, 3, 3)
|
| 624 |
+
r_yaw = torch.stack((cy, zero, sy, zero, one, zero, -sy, zero, cy), dim=-1).reshape(B, T, 3, 3)
|
| 625 |
+
c2w = torch.eye(4, device=pose.device, dtype=pose.dtype).view(1, 1, 4, 4).repeat(B, T, 1, 1)
|
| 626 |
+
c2w[:, :, :3, :3] = torch.matmul(r_yaw, r_pitch)
|
| 627 |
+
c2w[:, :, :3, 3] = torch.stack((x, y, z), dim=-1)
|
| 628 |
+
return c2w
|
| 629 |
+
|
| 630 |
+
def _build_frame_memory_geometry(self, frame_memory_segments, frame_memory_masks, frame_memory_pose, image_hw, grid_h, grid_w, device, dtype):
|
| 631 |
+
if frame_memory_segments is None or frame_memory_pose is None or image_hw is None:
|
| 632 |
+
return None
|
| 633 |
+
|
| 634 |
+
pose = frame_memory_pose.to(device=device, dtype=dtype)
|
| 635 |
+
B = pose.shape[0]
|
| 636 |
+
target_frames = int(frame_memory_segments.get("target", 0))
|
| 637 |
+
image_hw = image_hw if torch.is_tensor(image_hw) else torch.as_tensor(image_hw)
|
| 638 |
+
image_hw = image_hw.to(device=device, dtype=dtype)
|
| 639 |
+
if image_hw.ndim == 1:
|
| 640 |
+
image_hw = image_hw.unsqueeze(0)
|
| 641 |
+
if image_hw.shape[0] == 1 and B != 1:
|
| 642 |
+
image_hw = image_hw.expand(B, -1)
|
| 643 |
+
image_hw = image_hw.contiguous()
|
| 644 |
+
image_h, image_w = image_hw[:, 0].view(B, 1, 1), image_hw[:, 1].view(B, 1, 1)
|
| 645 |
+
|
| 646 |
+
# Token grid sets position count; image_hw keeps camera scaling in original image coordinates.
|
| 647 |
+
y = (torch.arange(grid_h, device=device, dtype=dtype).view(1, grid_h, 1) + 0.5) * (image_h / grid_h)
|
| 648 |
+
x = (torch.arange(grid_w, device=device, dtype=dtype).view(1, 1, grid_w) + 0.5) * (image_w / grid_w)
|
| 649 |
+
fx, fy = 0.35 * image_w, 0.35 * image_h
|
| 650 |
+
cx, cy = 0.5 * image_w, 0.5 * image_h
|
| 651 |
+
directions = torch.stack(
|
| 652 |
+
(
|
| 653 |
+
(-(x - cx) / fx).expand(-1, grid_h, -1),
|
| 654 |
+
(-(y - cy) / fy).expand(-1, -1, grid_w),
|
| 655 |
+
torch.ones((B, grid_h, grid_w), device=device, dtype=dtype),
|
| 656 |
+
),
|
| 657 |
+
dim=-1,
|
| 658 |
+
)
|
| 659 |
+
directions = F.normalize(directions, dim=-1)
|
| 660 |
+
query_rays = torch.cat([torch.zeros_like(directions), directions], dim=-1)[:, None].expand(-1, target_frames, -1, -1, -1)
|
| 661 |
+
intrinsics = torch.stack((fx[:, 0, 0], fy[:, 0, 0], cx[:, 0, 0], cy[:, 0, 0]), dim=-1)
|
| 662 |
+
|
| 663 |
+
c2w = self._frame_memory_pose_to_c2w(pose)
|
| 664 |
+
target_pose = pose[:, :target_frames]
|
| 665 |
+
target_c2w = c2w[:, :target_frames]
|
| 666 |
+
target_rot = target_c2w[:, :, :3, :3].transpose(-1, -2)
|
| 667 |
+
target_t = target_c2w[:, :, :3, 3]
|
| 668 |
+
target_w2c = torch.eye(4, device=device, dtype=dtype).view(1, 1, 4, 4).repeat(B, target_frames, 1, 1)
|
| 669 |
+
target_w2c[:, :, :3, :3] = target_rot
|
| 670 |
+
target_w2c[:, :, :3, 3] = -torch.matmul(target_rot, target_t.unsqueeze(-1)).squeeze(-1)
|
| 671 |
+
|
| 672 |
+
cache = {"query_rays": query_rays, "target_pose": target_pose, "image_hw": image_hw, "intrinsics": intrinsics}
|
| 673 |
+
cursor = target_frames
|
| 674 |
+
for stream_name in ("anchor", "dynamic", "revisit"):
|
| 675 |
+
stream_len = int(frame_memory_segments.get(stream_name, 0))
|
| 676 |
+
stream_pose = pose[:, cursor:cursor + stream_len]
|
| 677 |
+
relative_c2w = torch.matmul(target_w2c[:, :, None], c2w[:, None, cursor:cursor + stream_len])
|
| 678 |
+
rays_d = torch.einsum("bhwj,btmij->btmhwi", directions, relative_c2w[..., :3, :3])
|
| 679 |
+
rays_d = F.normalize(rays_d, dim=-1)
|
| 680 |
+
rays_o = relative_c2w[..., :3, 3][:, :, :, None, None, :].expand(-1, -1, -1, grid_h, grid_w, -1)
|
| 681 |
+
stream_mask = None if frame_memory_masks is None or frame_memory_masks.get(stream_name) is None else frame_memory_masks[stream_name].to(device=device, dtype=torch.bool)
|
| 682 |
+
cache[stream_name] = {
|
| 683 |
+
"query_rays": query_rays,
|
| 684 |
+
"relative_pose": stream_pose[:, None] - target_pose[:, :, None],
|
| 685 |
+
"relative_c2w": relative_c2w,
|
| 686 |
+
"relative_rays": torch.cat([torch.linalg.cross(rays_o, rays_d, dim=-1), rays_d], dim=-1),
|
| 687 |
+
"image_hw": image_hw,
|
| 688 |
+
"intrinsics": intrinsics,
|
| 689 |
+
"mask": stream_mask,
|
| 690 |
+
}
|
| 691 |
+
cursor += stream_len
|
| 692 |
+
return cache
|
| 693 |
+
|
| 694 |
def forward(self, x, t, action_cond=None, pose_cond=None, current_frame=None, mode=None,
|
| 695 |
reference_length=None, frame_idx=None, frame_memory_segments=None,
|
| 696 |
frame_memory_masks=None, frame_memory_pose=None, image_hw=None):
|
|
|
|
| 750 |
else:
|
| 751 |
pc = None
|
| 752 |
|
| 753 |
+
frame_memory_geometry = self._build_frame_memory_geometry(
|
| 754 |
+
frame_memory_segments,
|
| 755 |
+
frame_memory_masks,
|
| 756 |
+
frame_memory_pose,
|
| 757 |
+
image_hw,
|
| 758 |
+
x.shape[2],
|
| 759 |
+
x.shape[3],
|
| 760 |
+
x.device,
|
| 761 |
+
x.dtype,
|
| 762 |
+
)
|
| 763 |
+
|
| 764 |
for i, block in enumerate(self.blocks):
|
| 765 |
x = block(x, c, current_frame=current_frame, timestep=t, is_last_block= (i+1 == len(self.blocks)),
|
| 766 |
pose_cond=pc, mode=mode, c_action_cond=c_action_cond, reference_length=reference_length,
|
| 767 |
frame_memory_segments=frame_memory_segments,
|
| 768 |
frame_memory_masks=frame_memory_masks,
|
| 769 |
frame_memory_pose=frame_memory_pose,
|
| 770 |
+
image_hw=image_hw,
|
| 771 |
+
frame_memory_geometry=frame_memory_geometry) # (N, T, H, W, D)
|
| 772 |
x = self.final_layer(x, c) # (N, T, H, W, patch_size ** 2 * out_channels)
|
| 773 |
# unpatchify
|
| 774 |
x = rearrange(x, "b t h w d -> (b t) h w d")
|
tests/test_dememwm_temporal_attention.py
CHANGED
|
@@ -134,6 +134,80 @@ class DeMemWMTemporalAttentionTests(unittest.TestCase):
|
|
| 134 |
self.assertIs(spy.calls[0]["frame_memory_segments"], segments)
|
| 135 |
self.assertIs(spy.calls[0]["frame_memory_masks"], masks)
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
def test_frame_memory_reference_attention_masks_padded_keys(self):
|
| 138 |
attn = FrameMemoryReferenceAttention(hidden_size=1, num_heads=1)
|
| 139 |
with torch.no_grad():
|
|
|
|
| 134 |
self.assertIs(spy.calls[0]["frame_memory_segments"], segments)
|
| 135 |
self.assertIs(spy.calls[0]["frame_memory_masks"], masks)
|
| 136 |
|
| 137 |
+
def test_dit_builds_geometry_cache_once_and_threads_to_reference_attention(self):
|
| 138 |
+
class ZeroAttention(nn.Module):
|
| 139 |
+
def forward(self, x):
|
| 140 |
+
return torch.zeros_like(x)
|
| 141 |
+
|
| 142 |
+
class ZeroTemporalAttention(nn.Module):
|
| 143 |
+
def forward(self, x, frame_memory_segments=None, frame_memory_masks=None):
|
| 144 |
+
return torch.zeros_like(x)
|
| 145 |
+
|
| 146 |
+
class SpyReferenceAttention(nn.Module):
|
| 147 |
+
def __init__(self):
|
| 148 |
+
super().__init__()
|
| 149 |
+
self.geometry = []
|
| 150 |
+
|
| 151 |
+
def forward(self, target_hidden, memory_hidden, memory_mask=None, geometry_cache=None):
|
| 152 |
+
self.geometry.append(geometry_cache)
|
| 153 |
+
return torch.zeros_like(target_hidden)
|
| 154 |
+
|
| 155 |
+
model = DiT(
|
| 156 |
+
input_h=2,
|
| 157 |
+
input_w=2,
|
| 158 |
+
patch_size=1,
|
| 159 |
+
in_channels=1,
|
| 160 |
+
hidden_size=8,
|
| 161 |
+
depth=2,
|
| 162 |
+
num_heads=1,
|
| 163 |
+
mlp_ratio=1.0,
|
| 164 |
+
action_cond_dim=3,
|
| 165 |
+
pose_cond_dim=0,
|
| 166 |
+
reference_length=0,
|
| 167 |
+
use_memory_attention=True,
|
| 168 |
+
)
|
| 169 |
+
spies = []
|
| 170 |
+
for block in model.blocks:
|
| 171 |
+
block.s_attn = ZeroAttention()
|
| 172 |
+
block.t_attn = ZeroTemporalAttention()
|
| 173 |
+
anchor = SpyReferenceAttention()
|
| 174 |
+
dynamic = SpyReferenceAttention()
|
| 175 |
+
revisit = SpyReferenceAttention()
|
| 176 |
+
block.r_attn_anchor = anchor
|
| 177 |
+
block.r_attn_dynamic = dynamic
|
| 178 |
+
block.r_attn_revisit = revisit
|
| 179 |
+
spies.append((anchor, dynamic, revisit))
|
| 180 |
+
model.eval()
|
| 181 |
+
|
| 182 |
+
x = torch.zeros((1, 5, 1, 2, 2))
|
| 183 |
+
t = torch.zeros((1, 5), dtype=torch.long)
|
| 184 |
+
action_cond = torch.zeros((1, 5, 3))
|
| 185 |
+
frame_memory_pose = torch.zeros((1, 5, 5))
|
| 186 |
+
frame_memory_pose[0, :, 0] = torch.arange(5, dtype=torch.float32)
|
| 187 |
+
image_hw = torch.tensor([[4, 8]], dtype=torch.long)
|
| 188 |
+
segments = {"target": 2, "anchor": 1, "dynamic": 1, "revisit": 1}
|
| 189 |
+
masks = {"dynamic": torch.tensor([[False]])}
|
| 190 |
+
|
| 191 |
+
with torch.no_grad():
|
| 192 |
+
model(
|
| 193 |
+
x,
|
| 194 |
+
t,
|
| 195 |
+
action_cond,
|
| 196 |
+
frame_memory_segments=segments,
|
| 197 |
+
frame_memory_masks=masks,
|
| 198 |
+
frame_memory_pose=frame_memory_pose,
|
| 199 |
+
image_hw=image_hw,
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
anchor_cache = spies[0][0].geometry[0]
|
| 203 |
+
self.assertIs(anchor_cache, spies[1][0].geometry[0])
|
| 204 |
+
self.assertEqual(tuple(anchor_cache["query_rays"].shape), (1, 2, 2, 2, 6))
|
| 205 |
+
self.assertEqual(tuple(anchor_cache["relative_rays"].shape), (1, 2, 1, 2, 2, 6))
|
| 206 |
+
self.assertTrue(torch.equal(anchor_cache["image_hw"], torch.tensor([[4.0, 8.0]])))
|
| 207 |
+
self.assertTrue(torch.allclose(anchor_cache["intrinsics"], torch.tensor([[2.8, 1.4, 4.0, 2.0]])))
|
| 208 |
+
self.assertTrue(torch.equal(anchor_cache["relative_pose"][0, :, 0, 0], torch.tensor([2.0, 1.0])))
|
| 209 |
+
self.assertTrue(torch.equal(spies[0][1].geometry[0]["mask"], masks["dynamic"]))
|
| 210 |
+
|
| 211 |
def test_frame_memory_reference_attention_masks_padded_keys(self):
|
| 212 |
attn = FrameMemoryReferenceAttention(hidden_size=1, num_heads=1)
|
| 213 |
with torch.no_grad():
|