Add DeMemWM latent validation path
Browse files
algorithms/dememwm/df_video.py
CHANGED
|
@@ -880,9 +880,79 @@ class WorldMemMinecraft(DiffusionForcingBase):
|
|
| 880 |
memory_condition_length = self.memory_condition_length
|
| 881 |
preprocessed = self._preprocess_batch(batch)
|
| 882 |
if isinstance(preprocessed, Mapping):
|
| 883 |
-
|
| 884 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 885 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 886 |
xs_raw, conditions, pose_conditions, c2w_mat, frame_idx = preprocessed
|
| 887 |
|
| 888 |
|
|
|
|
| 880 |
memory_condition_length = self.memory_condition_length
|
| 881 |
preprocessed = self._preprocess_batch(batch)
|
| 882 |
if isinstance(preprocessed, Mapping):
|
| 883 |
+
xs = preprocessed["latents"]
|
| 884 |
+
target_length = preprocessed["target_length"]
|
| 885 |
+
batch_size = xs.shape[1]
|
| 886 |
+
conditions = preprocessed["action_conditions"].to(device=xs.device)
|
| 887 |
+
frame_indices = preprocessed["frame_indices"].to(device=xs.device)
|
| 888 |
+
frame_memory_pose = preprocessed["frame_memory_pose"].to(device=xs.device, dtype=xs.dtype)
|
| 889 |
+
image_hw = preprocessed["image_hw"].to(device=xs.device)
|
| 890 |
+
frame_memory_masks = {
|
| 891 |
+
key: mask.to(device=xs.device)
|
| 892 |
+
for key, mask in preprocessed["memory_masks"].items()
|
| 893 |
+
}
|
| 894 |
+
|
| 895 |
+
xs_pred = xs.clone()
|
| 896 |
+
# Single-window latent validation: only target frames are sampled;
|
| 897 |
+
# appended memory latents stay exactly as provided by the dataset.
|
| 898 |
+
xs_pred[:target_length] = torch.randn_like(xs_pred[:target_length]).clamp(
|
| 899 |
+
-self.clip_noise,
|
| 900 |
+
self.clip_noise,
|
| 901 |
+
)
|
| 902 |
+
scheduling_matrix = self._generate_scheduling_matrix(target_length)
|
| 903 |
+
memory_length = xs.shape[0] - target_length
|
| 904 |
+
memory_noise_levels = torch.zeros(
|
| 905 |
+
(memory_length, batch_size),
|
| 906 |
+
device=xs.device,
|
| 907 |
+
dtype=torch.long,
|
| 908 |
)
|
| 909 |
+
|
| 910 |
+
for m in range(scheduling_matrix.shape[0] - 1):
|
| 911 |
+
from_target = torch.as_tensor(
|
| 912 |
+
scheduling_matrix[m],
|
| 913 |
+
device=xs.device,
|
| 914 |
+
dtype=torch.long,
|
| 915 |
+
)[:, None].repeat(1, batch_size)
|
| 916 |
+
to_target = torch.as_tensor(
|
| 917 |
+
scheduling_matrix[m + 1],
|
| 918 |
+
device=xs.device,
|
| 919 |
+
dtype=torch.long,
|
| 920 |
+
)[:, None].repeat(1, batch_size)
|
| 921 |
+
from_noise_levels = torch.cat([from_target, memory_noise_levels], dim=0)
|
| 922 |
+
to_noise_levels = torch.cat([to_target, memory_noise_levels], dim=0)
|
| 923 |
+
|
| 924 |
+
sampled_target = self.diffusion_model.sample_step(
|
| 925 |
+
xs_pred,
|
| 926 |
+
conditions,
|
| 927 |
+
None,
|
| 928 |
+
from_noise_levels,
|
| 929 |
+
to_noise_levels,
|
| 930 |
+
current_frame=0,
|
| 931 |
+
mode=namespace,
|
| 932 |
+
reference_length=0,
|
| 933 |
+
frame_idx=frame_indices,
|
| 934 |
+
frame_memory_segments=preprocessed["memory_segments"],
|
| 935 |
+
frame_memory_masks=frame_memory_masks,
|
| 936 |
+
frame_memory_pose=frame_memory_pose,
|
| 937 |
+
image_hw=image_hw,
|
| 938 |
+
)
|
| 939 |
+
xs_pred[:target_length] = sampled_target[:target_length]
|
| 940 |
+
|
| 941 |
+
latent_loss = F.mse_loss(
|
| 942 |
+
xs_pred[:target_length],
|
| 943 |
+
xs[:target_length],
|
| 944 |
+
reduction="none",
|
| 945 |
+
)
|
| 946 |
+
target_mask = frame_memory_masks.get("target")
|
| 947 |
+
if target_mask is not None:
|
| 948 |
+
target_mask = rearrange(target_mask.to(dtype=latent_loss.dtype), "b t -> t b")
|
| 949 |
+
target_mask = target_mask.view(*target_mask.shape, *((1,) * (latent_loss.ndim - 2)))
|
| 950 |
+
latent_loss = (latent_loss * target_mask).sum() / target_mask.expand_as(latent_loss).sum().clamp_min(1.0)
|
| 951 |
+
else:
|
| 952 |
+
latent_loss = latent_loss.mean()
|
| 953 |
+
|
| 954 |
+
self.log(f"{namespace}/latent_mse", latent_loss.detach())
|
| 955 |
+
return latent_loss
|
| 956 |
xs_raw, conditions, pose_conditions, c2w_mat, frame_idx = preprocessed
|
| 957 |
|
| 958 |
|
tests/test_dememwm_latent_dataset.py
CHANGED
|
@@ -293,6 +293,104 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
|
|
| 293 |
self.assertEqual(harness.logged[0][0], "training/loss")
|
| 294 |
self.assertEqual(float(harness.logged[0][1]), 2.0)
|
| 295 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
def test_dataset_returns_target_anchor_dynamic_revisit_contract(self):
|
| 297 |
with tempfile.TemporaryDirectory() as tmp:
|
| 298 |
root = Path(tmp)
|
|
|
|
| 293 |
self.assertEqual(harness.logged[0][0], "training/loss")
|
| 294 |
self.assertEqual(float(harness.logged[0][1]), 2.0)
|
| 295 |
|
| 296 |
+
def test_validation_step_uses_latent_dict_target_sampling_and_metric(self):
|
| 297 |
+
from algorithms.dememwm.df_video import WorldMemMinecraft, _preprocess_dememwm_latent_batch
|
| 298 |
+
|
| 299 |
+
class FakeDiffusion:
|
| 300 |
+
def __init__(self):
|
| 301 |
+
self.calls = []
|
| 302 |
+
|
| 303 |
+
def sample_step(self, x, action_cond, pose_cond, curr_noise_level, next_noise_level, **kwargs):
|
| 304 |
+
self.calls.append(
|
| 305 |
+
{
|
| 306 |
+
"x": x.detach().clone(),
|
| 307 |
+
"action_cond": action_cond.detach().clone(),
|
| 308 |
+
"pose_cond": pose_cond,
|
| 309 |
+
"curr_noise_level": curr_noise_level.detach().clone(),
|
| 310 |
+
"next_noise_level": next_noise_level.detach().clone(),
|
| 311 |
+
"kwargs": kwargs,
|
| 312 |
+
}
|
| 313 |
+
)
|
| 314 |
+
target_length = kwargs["frame_memory_segments"]["target"]
|
| 315 |
+
sampled = x.clone()
|
| 316 |
+
sampled[:target_length] = float(len(self.calls))
|
| 317 |
+
sampled[target_length:] = 999.0
|
| 318 |
+
return sampled
|
| 319 |
+
|
| 320 |
+
class Harness:
|
| 321 |
+
def __init__(self):
|
| 322 |
+
self.memory_condition_length = 3
|
| 323 |
+
self.clip_noise = 0.0
|
| 324 |
+
self.diffusion_model = FakeDiffusion()
|
| 325 |
+
self.logged = []
|
| 326 |
+
self.horizons = []
|
| 327 |
+
|
| 328 |
+
def _preprocess_batch(self, batch):
|
| 329 |
+
return _preprocess_dememwm_latent_batch(batch)
|
| 330 |
+
|
| 331 |
+
def _generate_scheduling_matrix(self, horizon):
|
| 332 |
+
self.horizons.append(horizon)
|
| 333 |
+
return np.array([[2, 2], [1, 1], [0, 0]], dtype=np.int64)
|
| 334 |
+
|
| 335 |
+
def _generate_condition_indices(self, *args, **kwargs):
|
| 336 |
+
raise AssertionError("latent dict validation must not reselect memory")
|
| 337 |
+
|
| 338 |
+
def encode(self, xs):
|
| 339 |
+
raise AssertionError("latent dict validation must not VAE-encode inputs")
|
| 340 |
+
|
| 341 |
+
def decode(self, xs):
|
| 342 |
+
raise AssertionError("latent dict validation must not decode for the latent metric")
|
| 343 |
+
|
| 344 |
+
def log(self, name, value):
|
| 345 |
+
self.logged.append((name, value))
|
| 346 |
+
|
| 347 |
+
batch = {
|
| 348 |
+
"latents": torch.arange(5, dtype=torch.float32).view(1, 5, 1, 1, 1),
|
| 349 |
+
"actions": (100 + torch.arange(5, dtype=torch.float32)).view(1, 5, 1),
|
| 350 |
+
"poses": (200 + torch.arange(5, dtype=torch.float32)).view(1, 5, 1).repeat(1, 1, 5),
|
| 351 |
+
"frame_indices": (10 + torch.arange(5, dtype=torch.long)).view(1, 5),
|
| 352 |
+
"memory_segments": {
|
| 353 |
+
"target": torch.tensor([2]),
|
| 354 |
+
"anchor": torch.tensor([1]),
|
| 355 |
+
"dynamic": torch.tensor([1]),
|
| 356 |
+
"revisit": torch.tensor([1]),
|
| 357 |
+
},
|
| 358 |
+
"memory_masks": {
|
| 359 |
+
"target": torch.tensor([[True, False]]),
|
| 360 |
+
"anchor": torch.tensor([[True]]),
|
| 361 |
+
"dynamic": torch.tensor([[False]]),
|
| 362 |
+
"revisit": torch.tensor([[True]]),
|
| 363 |
+
},
|
| 364 |
+
"image_hw": torch.tensor([[360, 640]], dtype=torch.long),
|
| 365 |
+
}
|
| 366 |
+
harness = Harness()
|
| 367 |
+
|
| 368 |
+
loss = WorldMemMinecraft.validation_step(harness, batch, 0, namespace="test")
|
| 369 |
+
calls = harness.diffusion_model.calls
|
| 370 |
+
|
| 371 |
+
self.assertEqual(harness.horizons, [2])
|
| 372 |
+
self.assertEqual(len(calls), 2)
|
| 373 |
+
for call in calls:
|
| 374 |
+
self.assertEqual(tuple(call["x"].shape), (5, 1, 1, 1, 1))
|
| 375 |
+
self.assertEqual(call["x"][2:, 0, 0, 0, 0].tolist(), [2.0, 3.0, 4.0])
|
| 376 |
+
self.assertEqual(call["action_cond"][:, 0, 0].tolist(), [0.0, 101.0, 0.0, 0.0, 0.0])
|
| 377 |
+
self.assertIsNone(call["pose_cond"])
|
| 378 |
+
self.assertEqual(call["kwargs"]["reference_length"], 0)
|
| 379 |
+
self.assertEqual(call["kwargs"]["current_frame"], 0)
|
| 380 |
+
self.assertEqual(call["kwargs"]["mode"], "test")
|
| 381 |
+
self.assertEqual(call["kwargs"]["frame_idx"][:, 0].tolist(), [10, 11, 12, 13, 14])
|
| 382 |
+
self.assertEqual(call["kwargs"]["frame_memory_segments"], {"target": 2, "anchor": 1, "dynamic": 1, "revisit": 1})
|
| 383 |
+
self.assertEqual(call["kwargs"]["frame_memory_pose"][:, 0, 0].tolist(), [200.0, 201.0, 202.0, 203.0, 204.0])
|
| 384 |
+
self.assertEqual(call["kwargs"]["frame_memory_masks"]["dynamic"].tolist(), [[False]])
|
| 385 |
+
self.assertEqual(call["kwargs"]["image_hw"].tolist(), [[360, 640]])
|
| 386 |
+
self.assertEqual(calls[0]["curr_noise_level"][:, 0].tolist(), [2, 2, 0, 0, 0])
|
| 387 |
+
self.assertEqual(calls[0]["next_noise_level"][:, 0].tolist(), [1, 1, 0, 0, 0])
|
| 388 |
+
self.assertEqual(calls[1]["curr_noise_level"][:, 0].tolist(), [1, 1, 0, 0, 0])
|
| 389 |
+
self.assertEqual(calls[1]["next_noise_level"][:, 0].tolist(), [0, 0, 0, 0, 0])
|
| 390 |
+
self.assertEqual(float(loss), 4.0)
|
| 391 |
+
self.assertEqual(harness.logged[0][0], "test/latent_mse")
|
| 392 |
+
self.assertEqual(float(harness.logged[0][1]), 4.0)
|
| 393 |
+
|
| 394 |
def test_dataset_returns_target_anchor_dynamic_revisit_contract(self):
|
| 395 |
with tempfile.TemporaryDirectory() as tmp:
|
| 396 |
root = Path(tmp)
|