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 |
-
|
| 532 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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)
|