BonanDing commited on
Commit
e08128c
·
1 Parent(s): e35cf13

Add DeMemWM memory sheet diagnostics

Browse files
algorithms/dememwm/df_video.py CHANGED
@@ -10,7 +10,7 @@ import torch.distributed as dist
10
  import torch.nn.functional as F
11
  import torchvision.transforms.functional as TF
12
  from torchvision.transforms import InterpolationMode
13
- from PIL import Image
14
  from einops import rearrange
15
  from tqdm import tqdm
16
  from omegaconf import DictConfig, open_dict
@@ -675,6 +675,10 @@ class DeMemWMMinecraft(DiffusionForcingBase):
675
  self.require_pose_prediction = getattr(cfg, "require_pose_prediction", False)
676
  self.log_per_frame_metrics = bool(getattr(cfg, "log_per_frame_metrics", False))
677
  self.log_revisit_metrics = bool(getattr(cfg, "log_revisit_metrics", False))
 
 
 
 
678
  self.revisit_metrics_min_gap = int(getattr(cfg, "revisit_metrics_min_gap", 8))
679
  self.revisit_metrics_gap_bands = tuple(int(x) for x in getattr(cfg, "revisit_metrics_gap_bands", (32, 128)))
680
  self.revisit_metrics_self_crop_fraction = float(getattr(cfg, "revisit_metrics_self_crop_fraction", 0.5))
@@ -891,6 +895,126 @@ class DeMemWMMinecraft(DiffusionForcingBase):
891
  base = Path(self.local_save_dir) if self.local_save_dir is not None else Path.cwd() / "eval_artifacts"
892
  return base / "metrics"
893
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
894
  def _is_global_rank_zero(self) -> bool:
895
  return not (dist.is_available() and dist.is_initialized()) or dist.get_rank() == 0
896
 
@@ -1374,6 +1498,8 @@ class DeMemWMMinecraft(DiffusionForcingBase):
1374
  if dynamic_policy not in {"recent", "event_triggered"}:
1375
  raise ValueError(f"memory_selection.dynamic.selection_policy must be recent or event_triggered; got {dynamic_policy!r}")
1376
  event_dynamic_caches = [_new_online_event_cache(target_length) for _ in range(batch_size)] if dynamic_policy == "event_triggered" else None
 
 
1377
 
1378
  curr_frame = n_context_frames
1379
  generated_frames = 0
@@ -1456,6 +1582,17 @@ class DeMemWMMinecraft(DiffusionForcingBase):
1456
  else None
1457
  ),
1458
  )
 
 
 
 
 
 
 
 
 
 
 
1459
  selected_masks = {key: np.ones(len(value), dtype=bool) for key, value in selected.items()}
1460
  for key, indices in stream_indices.items():
1461
  count = indices.shape[1]
@@ -1562,6 +1699,8 @@ class DeMemWMMinecraft(DiffusionForcingBase):
1562
  latent_loss = xs_pred.new_tensor(0.0)
1563
 
1564
  self.log(f"{namespace}/latent_mse", latent_loss.detach())
 
 
1565
  if eval_start < target_length:
1566
  xs_pred_decode = self.decode(xs_pred[eval_start:target_length].to(target_poses.device))
1567
  xs_decode = self.decode(xs[eval_start:target_length].to(target_poses.device))
 
10
  import torch.nn.functional as F
11
  import torchvision.transforms.functional as TF
12
  from torchvision.transforms import InterpolationMode
13
+ from PIL import Image, ImageDraw
14
  from einops import rearrange
15
  from tqdm import tqdm
16
  from omegaconf import DictConfig, open_dict
 
675
  self.require_pose_prediction = getattr(cfg, "require_pose_prediction", False)
676
  self.log_per_frame_metrics = bool(getattr(cfg, "log_per_frame_metrics", False))
677
  self.log_revisit_metrics = bool(getattr(cfg, "log_revisit_metrics", False))
678
+ self.log_memory_selection_sheet = bool(getattr(cfg, "log_memory_selection_sheet", False))
679
+ self.memory_sheet_generated_frames = tuple(
680
+ int(x) for x in getattr(cfg, "memory_sheet_generated_frames", (1, 25, 50, 100, 200, 350, 500))
681
+ )
682
  self.revisit_metrics_min_gap = int(getattr(cfg, "revisit_metrics_min_gap", 8))
683
  self.revisit_metrics_gap_bands = tuple(int(x) for x in getattr(cfg, "revisit_metrics_gap_bands", (32, 128)))
684
  self.revisit_metrics_self_crop_fraction = float(getattr(cfg, "revisit_metrics_self_crop_fraction", 0.5))
 
895
  base = Path(self.local_save_dir) if self.local_save_dir is not None else Path.cwd() / "eval_artifacts"
896
  return base / "metrics"
897
 
898
+ def _memory_sheet_artifact_dir(self) -> Path:
899
+ base = Path(self.local_save_dir) if self.local_save_dir is not None else Path.cwd() / "eval_artifacts"
900
+ return base / "memory_selection_sheets"
901
+
902
+ def _memory_sheet_target_indices(self, eval_start: int, target_length: int) -> set[int]:
903
+ return {
904
+ int(eval_start) + int(frame) - 1
905
+ for frame in self.memory_sheet_generated_frames
906
+ if int(frame) > 0 and int(eval_start) + int(frame) - 1 < int(target_length)
907
+ }
908
+
909
+ def _save_memory_selection_sheets(
910
+ self,
911
+ xs_pred: torch.Tensor,
912
+ xs_gt: torch.Tensor,
913
+ frame_indices: torch.Tensor,
914
+ records: list[list[dict]],
915
+ eval_start: int,
916
+ batch_idx: int,
917
+ ) -> None:
918
+ if not self.log_memory_selection_sheet or not self._is_global_rank_zero():
919
+ return
920
+ output_dir = self._memory_sheet_artifact_dir()
921
+ output_dir.mkdir(parents=True, exist_ok=True)
922
+ max_slots = {"anchor": 2, "dynamic": 4, "revisit": 2}
923
+ tile_w, tile_h = 192, 108
924
+ label_h = 34
925
+ gap = 6
926
+
927
+ def decode_indices(source: torch.Tensor, batch_i: int, indices: list[int]) -> dict[int, Image.Image]:
928
+ if not indices:
929
+ return {}
930
+ unique = sorted(set(int(i) for i in indices))
931
+ latents = torch.stack([source[i, batch_i] for i in unique], dim=0).unsqueeze(1)
932
+ with torch.no_grad():
933
+ decoded = self.decode(latents.to(source.device))[:, 0].detach().cpu().float().clamp(0.0, 1.0)
934
+ return {idx: TF.to_pil_image(decoded[row]).resize((tile_w, tile_h), Image.BILINEAR) for row, idx in enumerate(unique)}
935
+
936
+ def make_tile(image: Image.Image | None, label: str, fill=(236, 236, 236)) -> Image.Image:
937
+ tile = Image.new("RGB", (tile_w, tile_h + label_h), fill)
938
+ if image is not None:
939
+ tile.paste(image.convert("RGB"), (0, label_h))
940
+ draw = ImageDraw.Draw(tile)
941
+ draw.rectangle((0, 0, tile_w, label_h), fill=(18, 18, 18))
942
+ for row, line in enumerate(label.split("\n")[:2]):
943
+ draw.text((5, 4 + row * 14), line, fill=(255, 255, 255))
944
+ return tile
945
+
946
+ summary_rows = []
947
+ for batch_i, batch_records in enumerate(records):
948
+ if not batch_records:
949
+ continue
950
+ target_indices = [int(row["target_index"]) for row in batch_records]
951
+ memory_gt = []
952
+ memory_pred = []
953
+ for row in batch_records:
954
+ for stream in _DEMEMWM_STREAM_KEYS:
955
+ for source_index in row["streams"][stream]:
956
+ (memory_gt if source_index < eval_start else memory_pred).append(int(source_index))
957
+ target_gt_images = decode_indices(xs_gt, batch_i, target_indices)
958
+ target_pred_images = decode_indices(xs_pred, batch_i, target_indices)
959
+ memory_images = {}
960
+ memory_images.update(decode_indices(xs_gt, batch_i, memory_gt))
961
+ memory_images.update(decode_indices(xs_pred, batch_i, memory_pred))
962
+
963
+ columns = [("target_gt", -1), ("target_pred", -1)]
964
+ for stream in _DEMEMWM_STREAM_KEYS:
965
+ columns.extend((stream, slot) for slot in range(max_slots[stream]))
966
+ sheet = Image.new(
967
+ "RGB",
968
+ (len(columns) * tile_w + (len(columns) - 1) * gap, len(batch_records) * (tile_h + label_h) + (len(batch_records) - 1) * gap),
969
+ (245, 245, 245),
970
+ )
971
+ for record_row, row in enumerate(batch_records):
972
+ y = record_row * (tile_h + label_h + gap)
973
+ target_index = int(row["target_index"])
974
+ generated_frame = target_index - int(eval_start) + 1
975
+ raw_target = int(row["target_frame"])
976
+ for col, (stream, slot) in enumerate(columns):
977
+ x = col * (tile_w + gap)
978
+ if stream == "target_gt":
979
+ image = target_gt_images.get(target_index)
980
+ label = f"gen{generated_frame:03d} raw{raw_target}\ntarget GT"
981
+ elif stream == "target_pred":
982
+ image = target_pred_images.get(target_index)
983
+ label = f"gen{generated_frame:03d} raw{raw_target}\ntarget pred"
984
+ else:
985
+ stream_indices = row["streams"][stream]
986
+ source_index = int(stream_indices[slot]) if slot < len(stream_indices) else -1
987
+ image = memory_images.get(source_index)
988
+ if source_index >= 0:
989
+ raw_source = int(frame_indices[source_index, batch_i].detach().cpu().item())
990
+ source_type = "ctx" if source_index < eval_start else "pred"
991
+ label = f"{stream}{slot} seq{source_index} raw{raw_source}\n{source_type} for gen{generated_frame:03d}"
992
+ summary_rows.append(
993
+ {
994
+ "batch": batch_idx,
995
+ "sample": batch_i,
996
+ "generated_frame": generated_frame,
997
+ "target_index": target_index,
998
+ "target_raw_frame": raw_target,
999
+ "stream": stream,
1000
+ "slot": slot,
1001
+ "source_index": source_index,
1002
+ "source_raw_frame": raw_source,
1003
+ "source_type": source_type,
1004
+ }
1005
+ )
1006
+ else:
1007
+ label = f"{stream}{slot}\nempty"
1008
+ sheet.paste(make_tile(image, label), (x, y))
1009
+ sheet.save(output_dir / f"batch{batch_idx:06d}_sample{batch_i:02d}_memory_streams.jpg", quality=95)
1010
+
1011
+ if summary_rows:
1012
+ csv_path = output_dir / f"batch{batch_idx:06d}_memory_streams.csv"
1013
+ with csv_path.open("w", newline="", encoding="utf-8") as handle:
1014
+ writer = csv.DictWriter(handle, fieldnames=list(summary_rows[0].keys()))
1015
+ writer.writeheader()
1016
+ writer.writerows(summary_rows)
1017
+
1018
  def _is_global_rank_zero(self) -> bool:
1019
  return not (dist.is_available() and dist.is_initialized()) or dist.get_rank() == 0
1020
 
 
1498
  if dynamic_policy not in {"recent", "event_triggered"}:
1499
  raise ValueError(f"memory_selection.dynamic.selection_policy must be recent or event_triggered; got {dynamic_policy!r}")
1500
  event_dynamic_caches = [_new_online_event_cache(target_length) for _ in range(batch_size)] if dynamic_policy == "event_triggered" else None
1501
+ memory_sheet_targets = self._memory_sheet_target_indices(n_context_frames, target_length) if self.log_memory_selection_sheet else set()
1502
+ memory_sheet_records = [[] for _ in range(batch_size)] if memory_sheet_targets else None
1503
 
1504
  curr_frame = n_context_frames
1505
  generated_frames = 0
 
1582
  else None
1583
  ),
1584
  )
1585
+ if memory_sheet_records is not None and int(target_positions[0]) in memory_sheet_targets:
1586
+ memory_sheet_records[batch_i].append(
1587
+ {
1588
+ "target_index": int(target_positions[0]),
1589
+ "target_frame": int(target_frame_indices[int(target_positions[0]), batch_i].detach().cpu().item()),
1590
+ "streams": {
1591
+ key: [int(v) for v in np.asarray(selected.get(key, []), dtype=np.int64) if 0 <= int(v) < curr_frame]
1592
+ for key in _DEMEMWM_STREAM_KEYS
1593
+ },
1594
+ }
1595
+ )
1596
  selected_masks = {key: np.ones(len(value), dtype=bool) for key, value in selected.items()}
1597
  for key, indices in stream_indices.items():
1598
  count = indices.shape[1]
 
1699
  latent_loss = xs_pred.new_tensor(0.0)
1700
 
1701
  self.log(f"{namespace}/latent_mse", latent_loss.detach())
1702
+ if memory_sheet_records is not None:
1703
+ self._save_memory_selection_sheets(xs_pred, xs, target_frame_indices, memory_sheet_records, eval_start, batch_idx)
1704
  if eval_start < target_length:
1705
  xs_pred_decode = self.decode(xs_pred[eval_start:target_length].to(target_poses.device))
1706
  xs_decode = self.decode(xs[eval_start:target_length].to(target_poses.device))
datasets/video/memory_selection.py CHANGED
@@ -329,18 +329,18 @@ def _select_anchor(candidates: np.ndarray, count: int, cfg, poses=None) -> np.nd
329
  poses_t = _as_pose_tensor(poses)
330
  candidates_t = torch.as_tensor(candidates.astype(np.int64), device=poses_t.device, dtype=torch.long)
331
  candidate_poses = poses_t.index_select(0, candidates_t).float()
332
- pairwise = torch.cdist(candidate_poses, candidate_poses)
 
 
 
 
 
333
  if not bool((pairwise > 0).any().item()):
334
  return candidates[:count].astype(np.int64)
335
 
336
  available = torch.ones((int(candidates_t.numel()),), device=poses_t.device, dtype=torch.bool)
337
- if count == 1:
338
- selected = [int(pairwise.mean(dim=1).argmax().item())]
339
- else:
340
- first, second = divmod(int(pairwise.argmax().item()), int(pairwise.shape[1]))
341
- selected = [int(first), int(second)]
342
- for idx in selected:
343
- available[idx] = False
344
 
345
  dists = pairwise[selected].min(dim=0).values.masked_fill(~available, float("-inf"))
346
  for _ in range(count - len(selected)):
 
329
  poses_t = _as_pose_tensor(poses)
330
  candidates_t = torch.as_tensor(candidates.astype(np.int64), device=poses_t.device, dtype=torch.long)
331
  candidate_poses = poses_t.index_select(0, candidates_t).float()
332
+ spatial = torch.cdist(candidate_poses[:, :3], candidate_poses[:, :3])
333
+ angular = torch.linalg.vector_norm(
334
+ _wrap_degrees(candidate_poses[:, None, 3:] - candidate_poses[None, :, 3:]),
335
+ dim=-1,
336
+ )
337
+ pairwise = torch.sqrt(spatial.square() + angular.square())
338
  if not bool((pairwise > 0).any().item()):
339
  return candidates[:count].astype(np.int64)
340
 
341
  available = torch.ones((int(candidates_t.numel()),), device=poses_t.device, dtype=torch.bool)
342
+ selected = [0]
343
+ available[0] = False
 
 
 
 
 
344
 
345
  dists = pairwise[selected].min(dim=0).values.masked_fill(~available, float("-inf"))
346
  for _ in range(count - len(selected)):