Use fp32 for DeMemWM Plucker geometry
Browse files
.exp_artifact/dememwm_remaining_fix_plan.md
CHANGED
|
@@ -285,7 +285,7 @@ Validate DeMemWM DiT stream boundaries
|
|
| 285 |
|
| 286 |
## Substep 5: Compute Plucker Geometry In fp32 Under Mixed Precision
|
| 287 |
|
| 288 |
-
Status: `[
|
| 289 |
|
| 290 |
Bug:
|
| 291 |
|
|
|
|
| 285 |
|
| 286 |
## Substep 5: Compute Plucker Geometry In fp32 Under Mixed Precision
|
| 287 |
|
| 288 |
+
Status: `[x]`
|
| 289 |
|
| 290 |
Bug:
|
| 291 |
|
algorithms/dememwm/models/dit.py
CHANGED
|
@@ -645,7 +645,7 @@ class DiT(nn.Module):
|
|
| 645 |
)
|
| 646 |
return None
|
| 647 |
|
| 648 |
-
pose = frame_memory_pose.to(device=device
|
| 649 |
if pose.ndim != 3 or pose.shape[-1] != 5:
|
| 650 |
_warn_frame_memory_geometry_once(
|
| 651 |
"malformed_pose_metadata",
|
|
@@ -665,7 +665,7 @@ class DiT(nn.Module):
|
|
| 665 |
)
|
| 666 |
return None
|
| 667 |
image_hw = image_hw if torch.is_tensor(image_hw) else torch.as_tensor(image_hw)
|
| 668 |
-
image_hw = image_hw.to(device=device
|
| 669 |
if image_hw.ndim == 1:
|
| 670 |
image_hw = image_hw.unsqueeze(0)
|
| 671 |
if image_hw.ndim != 2 or image_hw.shape[-1] < 2:
|
|
@@ -683,54 +683,64 @@ class DiT(nn.Module):
|
|
| 683 |
)
|
| 684 |
return None
|
| 685 |
image_hw = image_hw.contiguous()
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
(
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
cache[stream_name] = {
|
| 725 |
-
"query_rays": query_rays,
|
| 726 |
-
"relative_pose": stream_pose[:, None] - target_pose[:, :, None],
|
| 727 |
-
"relative_c2w": relative_c2w,
|
| 728 |
-
"relative_rays": torch.cat([torch.linalg.cross(rays_o, rays_d, dim=-1), rays_d], dim=-1),
|
| 729 |
-
"image_hw": image_hw,
|
| 730 |
-
"intrinsics": intrinsics,
|
| 731 |
-
"mask": stream_mask,
|
| 732 |
}
|
| 733 |
-
cursor
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
return cache
|
| 735 |
|
| 736 |
def forward(self, x, t, action_cond=None, pose_cond=None, current_frame=None, mode=None,
|
|
|
|
| 645 |
)
|
| 646 |
return None
|
| 647 |
|
| 648 |
+
pose = frame_memory_pose.to(device=device)
|
| 649 |
if pose.ndim != 3 or pose.shape[-1] != 5:
|
| 650 |
_warn_frame_memory_geometry_once(
|
| 651 |
"malformed_pose_metadata",
|
|
|
|
| 665 |
)
|
| 666 |
return None
|
| 667 |
image_hw = image_hw if torch.is_tensor(image_hw) else torch.as_tensor(image_hw)
|
| 668 |
+
image_hw = image_hw.to(device=device)
|
| 669 |
if image_hw.ndim == 1:
|
| 670 |
image_hw = image_hw.unsqueeze(0)
|
| 671 |
if image_hw.ndim != 2 or image_hw.shape[-1] < 2:
|
|
|
|
| 683 |
)
|
| 684 |
return None
|
| 685 |
image_hw = image_hw.contiguous()
|
| 686 |
+
# Mixed precision can round camera offsets before relative poses/rays are
|
| 687 |
+
# formed, so keep this local geometry block in fp32 and only cast caches.
|
| 688 |
+
with torch.autocast(device_type=device.type, enabled=False):
|
| 689 |
+
pose = pose.to(dtype=torch.float32)
|
| 690 |
+
image_hw = image_hw.to(dtype=torch.float32)
|
| 691 |
+
image_h, image_w = image_hw[:, 0].view(B, 1, 1), image_hw[:, 1].view(B, 1, 1)
|
| 692 |
+
|
| 693 |
+
# Token grid sets position count; image_hw keeps camera scaling in original image coordinates.
|
| 694 |
+
y = (torch.arange(grid_h, device=device, dtype=torch.float32).view(1, grid_h, 1) + 0.5) * (image_h / grid_h)
|
| 695 |
+
x = (torch.arange(grid_w, device=device, dtype=torch.float32).view(1, 1, grid_w) + 0.5) * (image_w / grid_w)
|
| 696 |
+
fx, fy = 0.35 * image_w, 0.35 * image_h
|
| 697 |
+
cx, cy = 0.5 * image_w, 0.5 * image_h
|
| 698 |
+
directions = torch.stack(
|
| 699 |
+
(
|
| 700 |
+
(-(x - cx) / fx).expand(-1, grid_h, -1),
|
| 701 |
+
(-(y - cy) / fy).expand(-1, -1, grid_w),
|
| 702 |
+
torch.ones((B, grid_h, grid_w), device=device, dtype=torch.float32),
|
| 703 |
+
),
|
| 704 |
+
dim=-1,
|
| 705 |
+
)
|
| 706 |
+
directions = F.normalize(directions, dim=-1)
|
| 707 |
+
query_rays = torch.cat([torch.zeros_like(directions), directions], dim=-1)[:, None].expand(-1, target_frames, -1, -1, -1)
|
| 708 |
+
intrinsics = torch.stack((fx[:, 0, 0], fy[:, 0, 0], cx[:, 0, 0], cy[:, 0, 0]), dim=-1)
|
| 709 |
+
|
| 710 |
+
c2w = self._frame_memory_pose_to_c2w(pose)
|
| 711 |
+
target_pose = pose[:, :target_frames]
|
| 712 |
+
target_c2w = c2w[:, :target_frames]
|
| 713 |
+
target_rot = target_c2w[:, :, :3, :3].transpose(-1, -2)
|
| 714 |
+
target_t = target_c2w[:, :, :3, 3]
|
| 715 |
+
target_w2c = torch.eye(4, device=device, dtype=torch.float32).view(1, 1, 4, 4).repeat(B, target_frames, 1, 1)
|
| 716 |
+
target_w2c[:, :, :3, :3] = target_rot
|
| 717 |
+
target_w2c[:, :, :3, 3] = -torch.matmul(target_rot, target_t.unsqueeze(-1)).squeeze(-1)
|
| 718 |
+
|
| 719 |
+
cache = {
|
| 720 |
+
"query_rays": query_rays.to(dtype=dtype),
|
| 721 |
+
"target_pose": target_pose.to(dtype=dtype),
|
| 722 |
+
"image_hw": image_hw.to(dtype=dtype),
|
| 723 |
+
"intrinsics": intrinsics.to(dtype=dtype),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
}
|
| 725 |
+
cursor = target_frames
|
| 726 |
+
for stream_name in ("anchor", "dynamic", "revisit"):
|
| 727 |
+
stream_len = int(frame_memory_segments.get(stream_name, 0))
|
| 728 |
+
stream_pose = pose[:, cursor:cursor + stream_len]
|
| 729 |
+
relative_c2w = torch.matmul(target_w2c[:, :, None], c2w[:, None, cursor:cursor + stream_len])
|
| 730 |
+
rays_d = torch.einsum("bhwj,btmij->btmhwi", directions, relative_c2w[..., :3, :3])
|
| 731 |
+
rays_d = F.normalize(rays_d, dim=-1)
|
| 732 |
+
rays_o = relative_c2w[..., :3, 3][:, :, :, None, None, :].expand(-1, -1, -1, grid_h, grid_w, -1)
|
| 733 |
+
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)
|
| 734 |
+
cache[stream_name] = {
|
| 735 |
+
"query_rays": query_rays.to(dtype=dtype),
|
| 736 |
+
"relative_pose": (stream_pose[:, None] - target_pose[:, :, None]).to(dtype=dtype),
|
| 737 |
+
"relative_c2w": relative_c2w.to(dtype=dtype),
|
| 738 |
+
"relative_rays": torch.cat([torch.linalg.cross(rays_o, rays_d, dim=-1), rays_d], dim=-1).to(dtype=dtype),
|
| 739 |
+
"image_hw": image_hw.to(dtype=dtype),
|
| 740 |
+
"intrinsics": intrinsics.to(dtype=dtype),
|
| 741 |
+
"mask": stream_mask,
|
| 742 |
+
}
|
| 743 |
+
cursor += stream_len
|
| 744 |
return cache
|
| 745 |
|
| 746 |
def forward(self, x, t, action_cond=None, pose_cond=None, current_frame=None, mode=None,
|
tests/test_dememwm_temporal_attention.py
CHANGED
|
@@ -373,6 +373,56 @@ class DeMemWMTemporalAttentionTests(unittest.TestCase):
|
|
| 373 |
self.assertTrue(torch.equal(anchor_cache["relative_pose"][0, :, 0, 0], torch.tensor([2.0, 1.0])))
|
| 374 |
self.assertTrue(torch.equal(spies[0][1].geometry[0]["mask"], masks["dynamic"]))
|
| 375 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
def test_frame_memory_reference_attention_masks_padded_keys(self):
|
| 377 |
attn = FrameMemoryReferenceAttention(hidden_size=1, num_heads=1)
|
| 378 |
with torch.no_grad():
|
|
|
|
| 373 |
self.assertTrue(torch.equal(anchor_cache["relative_pose"][0, :, 0, 0], torch.tensor([2.0, 1.0])))
|
| 374 |
self.assertTrue(torch.equal(spies[0][1].geometry[0]["mask"], masks["dynamic"]))
|
| 375 |
|
| 376 |
+
def test_frame_memory_geometry_uses_fp32_math_then_returns_activation_dtype(self):
|
| 377 |
+
model = DiT(
|
| 378 |
+
input_h=2,
|
| 379 |
+
input_w=2,
|
| 380 |
+
patch_size=1,
|
| 381 |
+
in_channels=1,
|
| 382 |
+
hidden_size=8,
|
| 383 |
+
depth=1,
|
| 384 |
+
num_heads=1,
|
| 385 |
+
mlp_ratio=1.0,
|
| 386 |
+
action_cond_dim=3,
|
| 387 |
+
pose_cond_dim=0,
|
| 388 |
+
reference_length=0,
|
| 389 |
+
use_plucker=True,
|
| 390 |
+
use_memory_attention=True,
|
| 391 |
+
)
|
| 392 |
+
device = torch.device("cpu")
|
| 393 |
+
segments = {"target": 1, "anchor": 1, "dynamic": 0, "revisit": 0}
|
| 394 |
+
frame_memory_pose = torch.zeros((1, 2, 5), device=device, dtype=torch.float32)
|
| 395 |
+
frame_memory_pose[0, 0, 0] = 10000.0
|
| 396 |
+
frame_memory_pose[0, 1, 0] = 10001.0
|
| 397 |
+
image_hw = torch.tensor([[4, 8]], device=device, dtype=torch.long)
|
| 398 |
+
|
| 399 |
+
with torch.autocast(device_type=device.type, dtype=torch.bfloat16):
|
| 400 |
+
cache = model._build_frame_memory_geometry(
|
| 401 |
+
segments,
|
| 402 |
+
None,
|
| 403 |
+
frame_memory_pose,
|
| 404 |
+
image_hw,
|
| 405 |
+
grid_h=2,
|
| 406 |
+
grid_w=2,
|
| 407 |
+
device=device,
|
| 408 |
+
dtype=torch.float16,
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
anchor_cache = cache["anchor"]
|
| 412 |
+
for tensor in (
|
| 413 |
+
cache["query_rays"],
|
| 414 |
+
cache["target_pose"],
|
| 415 |
+
cache["image_hw"],
|
| 416 |
+
cache["intrinsics"],
|
| 417 |
+
anchor_cache["relative_pose"],
|
| 418 |
+
anchor_cache["relative_c2w"],
|
| 419 |
+
anchor_cache["relative_rays"],
|
| 420 |
+
):
|
| 421 |
+
self.assertEqual(tensor.dtype, torch.float16)
|
| 422 |
+
self.assertTrue(torch.isfinite(tensor).all().item())
|
| 423 |
+
self.assertEqual(anchor_cache["relative_pose"][0, 0, 0, 0].item(), 1.0)
|
| 424 |
+
self.assertEqual(anchor_cache["relative_c2w"][0, 0, 0, 0, 3].item(), 1.0)
|
| 425 |
+
|
| 426 |
def test_frame_memory_reference_attention_masks_padded_keys(self):
|
| 427 |
attn = FrameMemoryReferenceAttention(hidden_size=1, num_heads=1)
|
| 428 |
with torch.no_grad():
|