BonanDing commited on
Commit
448fb39
·
1 Parent(s): 39e6090

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
- raise NotImplementedError(
884
- "DeMemWM latent validation needs Step 5.6 target-only sampling before evaluation."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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)