BonanDing commited on
Commit
39e6090
·
1 Parent(s): 2eafd0f

Add DeMemWM latent training step

Browse files
algorithms/dememwm/df_video.py CHANGED
@@ -528,9 +528,56 @@ class WorldMemMinecraft(DiffusionForcingBase):
528
  """
529
  preprocessed = self._preprocess_batch(batch)
530
  if isinstance(preprocessed, Mapping):
531
- raise NotImplementedError(
532
- "DeMemWM latent training_step needs Step 5.2/5.5 target/memory split before diffusion forward."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
534
  xs, conditions, pose_conditions, c2w_mat, frame_idx = preprocessed
535
 
536
  if self.use_plucker:
 
528
  """
529
  preprocessed = self._preprocess_batch(batch)
530
  if isinstance(preprocessed, Mapping):
531
+ xs = preprocessed["latents"]
532
+ target_length = preprocessed["target_length"]
533
+ conditions = preprocessed["action_conditions"].to(device=xs.device)
534
+ frame_indices = preprocessed["frame_indices"].to(device=xs.device)
535
+ frame_memory_pose = preprocessed["frame_memory_pose"].to(device=xs.device, dtype=xs.dtype)
536
+ image_hw = preprocessed["image_hw"].to(device=xs.device)
537
+ frame_memory_masks = {
538
+ key: mask.to(device=xs.device)
539
+ for key, mask in preprocessed["memory_masks"].items()
540
+ }
541
+
542
+ target_noise_levels = self._generate_noise_levels(xs[:target_length])
543
+ memory_noise_levels = [
544
+ torch.full(
545
+ (preprocessed["stream_lengths"][key], xs.shape[1]),
546
+ self.diffusion_model.stabilization_level,
547
+ device=xs.device,
548
+ dtype=target_noise_levels.dtype,
549
+ )
550
+ for key in _DEMEMWM_STREAM_KEYS
551
+ ]
552
+ # Appended memory stays at a fixed stabilization level for the first
553
+ # latent smoke path; routed/SNR-shifted policies belong in a later step.
554
+ noise_levels = torch.cat([target_noise_levels, *memory_noise_levels], dim=0)
555
+
556
+ _, loss = self.diffusion_model(
557
+ xs,
558
+ conditions,
559
+ None,
560
+ noise_levels=noise_levels,
561
+ reference_length=0,
562
+ frame_idx=frame_indices,
563
+ frame_memory_segments=preprocessed["memory_segments"],
564
+ frame_memory_masks=frame_memory_masks,
565
+ frame_memory_pose=frame_memory_pose,
566
+ image_hw=image_hw,
567
  )
568
+ loss = loss[:target_length]
569
+ target_mask = frame_memory_masks.get("target")
570
+ if target_mask is not None:
571
+ target_mask = rearrange(target_mask.to(dtype=loss.dtype), "b t -> t b")
572
+ target_mask = target_mask.view(*target_mask.shape, *((1,) * (loss.ndim - 2)))
573
+ loss = (loss * target_mask).sum() / target_mask.expand_as(loss).sum().clamp_min(1.0)
574
+ else:
575
+ loss = self.reweight_loss(loss, None)
576
+
577
+ if batch_idx % 20 == 0:
578
+ self.log("training/loss", loss.cpu())
579
+
580
+ return {"loss": loss}
581
  xs, conditions, pose_conditions, c2w_mat, frame_idx = preprocessed
582
 
583
  if self.use_plucker:
tests/test_dememwm_latent_dataset.py CHANGED
@@ -218,6 +218,81 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
218
  self.assertEqual(tuple(preprocessed["image_hw"].shape), (2, 2))
219
  self.assertEqual(preprocessed["image_hw"].tolist(), [[720, 1280], [720, 1280]])
220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  def test_dataset_returns_target_anchor_dynamic_revisit_contract(self):
222
  with tempfile.TemporaryDirectory() as tmp:
223
  root = Path(tmp)
 
218
  self.assertEqual(tuple(preprocessed["image_hw"].shape), (2, 2))
219
  self.assertEqual(preprocessed["image_hw"].tolist(), [[720, 1280], [720, 1280]])
220
 
221
+ def test_training_step_uses_latent_dict_target_loss_and_memory_metadata(self):
222
+ from algorithms.dememwm.df_video import WorldMemMinecraft, _preprocess_dememwm_latent_batch
223
+
224
+ class FakeDiffusion:
225
+ stabilization_level = 7
226
+
227
+ def __call__(self, x, action_cond, pose_cond, **kwargs):
228
+ self.x = x
229
+ self.action_cond = action_cond
230
+ self.pose_cond = pose_cond
231
+ self.kwargs = kwargs
232
+ loss = (torch.arange(x.shape[0], device=x.device, dtype=x.dtype) + 2).view(-1, 1, 1, 1, 1)
233
+ return x, loss.expand_as(x).clone()
234
+
235
+ class Harness:
236
+ def __init__(self):
237
+ self.diffusion_model = FakeDiffusion()
238
+ self.logged = []
239
+
240
+ def _preprocess_batch(self, batch):
241
+ return _preprocess_dememwm_latent_batch(batch)
242
+
243
+ def _generate_noise_levels(self, xs):
244
+ self.noise_input_shape = tuple(xs.shape)
245
+ return torch.arange(xs.shape[0], device=xs.device, dtype=torch.long).view(-1, 1)
246
+
247
+ def encode(self, xs):
248
+ raise AssertionError("latent dict training must not VAE-encode inputs")
249
+
250
+ def reweight_loss(self, loss, weight=None):
251
+ raise AssertionError("target mask path should reduce the latent dict loss directly")
252
+
253
+ def log(self, name, value):
254
+ self.logged.append((name, value))
255
+
256
+ batch = {
257
+ "latents": torch.arange(5, dtype=torch.float32).view(1, 5, 1, 1, 1),
258
+ "actions": (100 + torch.arange(5, dtype=torch.float32)).view(1, 5, 1),
259
+ "poses": (200 + torch.arange(5, dtype=torch.float32)).view(1, 5, 1).repeat(1, 1, 5),
260
+ "frame_indices": (10 + torch.arange(5, dtype=torch.long)).view(1, 5),
261
+ "memory_segments": {
262
+ "target": torch.tensor([2]),
263
+ "anchor": torch.tensor([1]),
264
+ "dynamic": torch.tensor([1]),
265
+ "revisit": torch.tensor([1]),
266
+ },
267
+ "memory_masks": {
268
+ "target": torch.tensor([[True, False]]),
269
+ "anchor": torch.tensor([[True]]),
270
+ "dynamic": torch.tensor([[False]]),
271
+ "revisit": torch.tensor([[True]]),
272
+ },
273
+ "image_hw": torch.tensor([[360, 640]], dtype=torch.long),
274
+ }
275
+ harness = Harness()
276
+
277
+ output = WorldMemMinecraft.training_step(harness, batch, 0)
278
+ call = harness.diffusion_model
279
+
280
+ self.assertEqual(harness.noise_input_shape, (2, 1, 1, 1, 1))
281
+ self.assertTrue(torch.equal(call.x[:, 0, 0, 0, 0], torch.arange(5, dtype=torch.float32)))
282
+ self.assertEqual(call.action_cond[:, 0, 0].tolist(), [0.0, 101.0, 0.0, 0.0, 0.0])
283
+ self.assertIsNone(call.pose_cond)
284
+ self.assertEqual(call.kwargs["reference_length"], 0)
285
+ self.assertEqual(call.kwargs["frame_memory_segments"], {"target": 2, "anchor": 1, "dynamic": 1, "revisit": 1})
286
+ self.assertEqual(call.kwargs["noise_levels"][:, 0].tolist(), [0, 1, 7, 7, 7])
287
+ self.assertEqual(call.kwargs["frame_idx"][:, 0].tolist(), [10, 11, 12, 13, 14])
288
+ self.assertEqual(call.kwargs["frame_memory_pose"][:, 0, 0].tolist(), [200.0, 201.0, 202.0, 203.0, 204.0])
289
+ self.assertEqual(call.kwargs["image_hw"].tolist(), [[360, 640]])
290
+ self.assertEqual(call.kwargs["frame_memory_masks"]["target"].device, call.x.device)
291
+ self.assertEqual(call.kwargs["frame_memory_masks"]["dynamic"].tolist(), [[False]])
292
+ self.assertEqual(float(output["loss"]), 2.0)
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)