mmmmmcyaaa commited on
Commit
5cd1287
·
verified ·
1 Parent(s): ffafc74

Complete shared baseline runtime dependencies

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. external_baselines/frameskip_adapted/build_required_draws.py +0 -15
  2. external_baselines/frameskip_adapted/finalize_cache.py +1 -11
  3. external_baselines/frameskip_adapted/train_frameskip_continuation.py +1 -1
  4. external_baselines/frameskip_robotwin_adapted/build_robotwin_cache.py +211 -0
  5. external_baselines/frameskip_robotwin_adapted/train_frameskip_robotwin_continuation.py +120 -0
  6. external_baselines/frameskip_robotwin_adapted/validate_assets.py +68 -0
  7. external_baselines/keyframe_focused_adapted/build_ape_cache.py +485 -0
  8. external_baselines/keyframe_focused_adapted/configs/keyframe_focused_seed117465_20k_to_20500.yaml +118 -0
  9. external_baselines/keyframe_focused_adapted/keyframe_cache.py +145 -0
  10. external_baselines/keyframe_focused_adapted/train_keyframe_continuation.py +85 -0
  11. external_baselines/keyframe_focused_adapted/validate_cache_support.py +105 -0
  12. external_baselines/loss_is_active/train_active_loss_is_continuation.py +330 -0
  13. external_baselines/loss_is_adapted/configs/loss_is_seed117465_20k_to_20500.yaml +122 -0
  14. external_baselines/loss_is_adapted/loss_is.py +48 -0
  15. external_baselines/loss_is_adapted/train_loss_is_continuation.py +293 -0
  16. external_baselines/qwen_adapted/train_qwen_frameskip_continuation.py +129 -0
  17. external_baselines/qwen_robotwin_adapted/register_qwen_robotwin_smoke.py +17 -0
  18. external_baselines/qwen_robotwin_adapted/train_qwen_robotwin_frameskip.py +22 -0
  19. external_baselines/qwen_robotwin_adapted/train_qwen_robotwin_keyframe.py +22 -0
  20. external_baselines/qwen_robotwin_adapted/train_qwen_robotwin_loss_is.py +22 -0
  21. external_baselines/settings/florence_bridge/frameskip.yaml +71 -0
  22. external_baselines/settings/florence_bridge/keyframe.yaml +70 -0
  23. external_baselines/settings/florence_bridge/loss_is_active.yaml +117 -0
  24. external_baselines/settings/florence_robotwin/frameskip.yaml +109 -0
  25. external_baselines/settings/florence_robotwin/keyframe.yaml +110 -0
  26. external_baselines/settings/florence_robotwin/loss_is_active.yaml +114 -0
  27. external_baselines/settings/qwen_bridge/frameskip.yaml +93 -0
  28. external_baselines/settings/qwen_bridge/keyframe.yaml +94 -0
  29. external_baselines/settings/qwen_bridge/loss_is_active.yaml +99 -0
  30. external_baselines/settings/qwen_robotwin/frameskip.yaml +107 -0
  31. external_baselines/settings/qwen_robotwin/keyframe.yaml +108 -0
  32. external_baselines/settings/qwen_robotwin/loss_is_active.yaml +112 -0
  33. starVLA/dataloader/__init__.py +2 -7
  34. starVLA/dataloader/gr00t_lerobot/data_config.py +3 -2
  35. starVLA/dataloader/gr00t_lerobot/datasets.py +62 -37
  36. starVLA/dataloader/gr00t_lerobot/transform/state_action.py +1 -0
  37. starVLA/dataloader/infoflow_sampler.py +4 -338
  38. starVLA/dataloader/lerobot_datasets.py +2 -3
  39. starVLA/dataloader/relative_pose.py +96 -0
  40. starVLA/dataloader/temporal_proposal.py +4 -227
  41. starVLA/model/framework/VLM4A/PI0.py +653 -0
  42. starVLA/model/framework/VLM4A/PI05.py +58 -0
  43. starVLA/model/framework/VLM4A/QwenFast.py +174 -0
  44. starVLA/model/framework/VLM4A/QwenGR00T.py +38 -3
  45. starVLA/model/framework/base_framework.py +2 -1
  46. starVLA/model/framework/share_tools.py +6 -15
  47. starVLA/model/modules/action_model/GR00T_ActionHeader.py +24 -2
  48. starVLA/model/modules/action_model/OpenPI_ActionHead.py +290 -0
  49. starVLA/model/modules/action_model/__init__.py +1 -0
  50. starVLA/model/modules/action_model/flow_matching_head/cross_attention_dit.py +8 -5
external_baselines/frameskip_adapted/build_required_draws.py CHANGED
@@ -4,18 +4,12 @@
4
  from __future__ import annotations
5
 
6
  import argparse
7
- import hashlib
8
  import json
9
  from pathlib import Path
10
 
11
  from starVLA.dataloader.infoflow_sampler import LogicalDraw, ValidAnchorManifest
12
 
13
 
14
- def canonical_sha256(payload: object) -> str:
15
- encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
16
- return hashlib.sha256(encoded).hexdigest()
17
-
18
-
19
  def main() -> None:
20
  parser = argparse.ArgumentParser()
21
  parser.add_argument("--manifest", required=True)
@@ -38,15 +32,10 @@ def main() -> None:
38
  }
39
  pruned_draws = 0
40
  full_draws = 0
41
- trace_digest = hashlib.sha256()
42
-
43
  for global_draw_id in range(start_draw, end_draw):
44
  microbatch = (global_draw_id - start_draw) // args.global_microbatch_size
45
  is_pruned = microbatch % period < args.pruned_batches
46
  trace = manifest.sample(args.seed, LogicalDraw(0, global_draw_id))
47
- trace_digest.update(
48
- json.dumps(trace.to_dict(), sort_keys=True, separators=(",", ":")).encode()
49
- )
50
  if is_pruned:
51
  required[trace.dataset_name].add(trace.trajectory_id)
52
  pruned_draws += 1
@@ -57,7 +46,6 @@ def main() -> None:
57
  "schema_version": 1,
58
  "method": "FrameSkip-adapted (Gaussian-TPI fallback)",
59
  "manifest_path": str(Path(args.manifest).resolve()),
60
- "manifest_sha256": manifest.sha256,
61
  "sampling_seed": args.seed,
62
  "start_step": args.start_step,
63
  "updates": args.updates,
@@ -69,7 +57,6 @@ def main() -> None:
69
  "full_batches": args.full_batches,
70
  "pruned_draws": pruned_draws,
71
  "full_draws": full_draws,
72
- "logical_trace_sha256": trace_digest.hexdigest(),
73
  "datasets": {
74
  name: sorted(ids, key=lambda value: (type(value).__name__, str(value)))
75
  for name, ids in required.items()
@@ -78,13 +65,11 @@ def main() -> None:
78
  payload["required_trajectory_count"] = sum(
79
  len(values) for values in payload["datasets"].values()
80
  )
81
- payload["sha256"] = canonical_sha256(payload)
82
  destination = Path(args.output)
83
  destination.parent.mkdir(parents=True, exist_ok=True)
84
  destination.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
85
  print(json.dumps({
86
  "output": str(destination),
87
- "sha256": payload["sha256"],
88
  "pruned_draws": pruned_draws,
89
  "full_draws": full_draws,
90
  "required_trajectory_count": payload["required_trajectory_count"],
 
4
  from __future__ import annotations
5
 
6
  import argparse
 
7
  import json
8
  from pathlib import Path
9
 
10
  from starVLA.dataloader.infoflow_sampler import LogicalDraw, ValidAnchorManifest
11
 
12
 
 
 
 
 
 
13
  def main() -> None:
14
  parser = argparse.ArgumentParser()
15
  parser.add_argument("--manifest", required=True)
 
32
  }
33
  pruned_draws = 0
34
  full_draws = 0
 
 
35
  for global_draw_id in range(start_draw, end_draw):
36
  microbatch = (global_draw_id - start_draw) // args.global_microbatch_size
37
  is_pruned = microbatch % period < args.pruned_batches
38
  trace = manifest.sample(args.seed, LogicalDraw(0, global_draw_id))
 
 
 
39
  if is_pruned:
40
  required[trace.dataset_name].add(trace.trajectory_id)
41
  pruned_draws += 1
 
46
  "schema_version": 1,
47
  "method": "FrameSkip-adapted (Gaussian-TPI fallback)",
48
  "manifest_path": str(Path(args.manifest).resolve()),
 
49
  "sampling_seed": args.seed,
50
  "start_step": args.start_step,
51
  "updates": args.updates,
 
57
  "full_batches": args.full_batches,
58
  "pruned_draws": pruned_draws,
59
  "full_draws": full_draws,
 
60
  "datasets": {
61
  name: sorted(ids, key=lambda value: (type(value).__name__, str(value)))
62
  for name, ids in required.items()
 
65
  payload["required_trajectory_count"] = sum(
66
  len(values) for values in payload["datasets"].values()
67
  )
 
68
  destination = Path(args.output)
69
  destination.parent.mkdir(parents=True, exist_ok=True)
70
  destination.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
71
  print(json.dumps({
72
  "output": str(destination),
 
73
  "pruned_draws": pruned_draws,
74
  "full_draws": full_draws,
75
  "required_trajectory_count": payload["required_trajectory_count"],
external_baselines/frameskip_adapted/finalize_cache.py CHANGED
@@ -1,20 +1,13 @@
1
  #!/usr/bin/env python3
2
- """Validate and merge FrameSkip cache shards into one hash-bound cache."""
3
 
4
  from __future__ import annotations
5
 
6
  import argparse
7
- import hashlib
8
  import json
9
  from pathlib import Path
10
 
11
 
12
- def canonical_sha256(payload: object) -> str:
13
- return hashlib.sha256(
14
- json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
15
- ).hexdigest()
16
-
17
-
18
  def main() -> None:
19
  parser = argparse.ArgumentParser()
20
  parser.add_argument("--required-draws", required=True)
@@ -52,7 +45,6 @@ def main() -> None:
52
  "schema_version": 1,
53
  "method": "FrameSkip-adapted (Gaussian-TPI fallback)",
54
  "upstream_commit": "965becf63e9cd5cc339fbf7fde40d1db47efa67a",
55
- "required_draws_sha256": required["sha256"],
56
  "retention_ratio": 0.2,
57
  "importance": {
58
  "calculator": "GripperAwareImportanceCalculator",
@@ -79,13 +71,11 @@ def main() -> None:
79
  ],
80
  "source_shards": shard_paths,
81
  }
82
- payload["sha256"] = canonical_sha256(payload)
83
  output = Path(args.output)
84
  output.parent.mkdir(parents=True, exist_ok=True)
85
  output.write_text(json.dumps(payload, separators=(",", ":")) + "\n", encoding="utf-8")
86
  print(json.dumps({
87
  "output": str(output),
88
- "sha256": payload["sha256"],
89
  "records": len(payload["records"]),
90
  "actual_ratio_min": min(r["actual_ratio"] for r in payload["records"]),
91
  "actual_ratio_max": max(r["actual_ratio"] for r in payload["records"]),
 
1
  #!/usr/bin/env python3
2
+ """Validate and merge FrameSkip cache shards into one portable cache."""
3
 
4
  from __future__ import annotations
5
 
6
  import argparse
 
7
  import json
8
  from pathlib import Path
9
 
10
 
 
 
 
 
 
 
11
  def main() -> None:
12
  parser = argparse.ArgumentParser()
13
  parser.add_argument("--required-draws", required=True)
 
45
  "schema_version": 1,
46
  "method": "FrameSkip-adapted (Gaussian-TPI fallback)",
47
  "upstream_commit": "965becf63e9cd5cc339fbf7fde40d1db47efa67a",
 
48
  "retention_ratio": 0.2,
49
  "importance": {
50
  "calculator": "GripperAwareImportanceCalculator",
 
71
  ],
72
  "source_shards": shard_paths,
73
  }
 
74
  output = Path(args.output)
75
  output.parent.mkdir(parents=True, exist_ok=True)
76
  output.write_text(json.dumps(payload, separators=(",", ":")) + "\n", encoding="utf-8")
77
  print(json.dumps({
78
  "output": str(output),
 
79
  "records": len(payload["records"]),
80
  "actual_ratio_min": min(r["actual_ratio"] for r in payload["records"]),
81
  "actual_ratio_max": max(r["actual_ratio"] for r in payload["records"]),
external_baselines/frameskip_adapted/train_frameskip_continuation.py CHANGED
@@ -68,7 +68,7 @@ def install_adapter(cache_path: str, start_global_draw_id: int) -> None:
68
  dataset.get_step_data = original_get_step_data
69
  sample["sampling_trace"]["frameskip"] = {
70
  "method": cache_payload["method"],
71
- "cache_sha256": cache_payload["sha256"],
72
  "is_pruned_minibatch": is_pruned,
73
  "logical_base_index": logical_index,
74
  "physical_base_index": actual_index,
 
68
  dataset.get_step_data = original_get_step_data
69
  sample["sampling_trace"]["frameskip"] = {
70
  "method": cache_payload["method"],
71
+ "cache_record_count": len(cache_payload["records"]),
72
  "is_pruned_minibatch": is_pruned,
73
  "logical_base_index": logical_index,
74
  "physical_base_index": actual_index,
external_baselines/frameskip_robotwin_adapted/build_robotwin_cache.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Build a RoboTwin FrameSkip cache with the locked upstream scoring code."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import importlib.util
8
+ import json
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+ import pyarrow.parquet as parquet
14
+ import timm
15
+ from PIL import Image
16
+
17
+ from starVLA.dataloader.gr00t_lerobot.video import get_frames_by_timestamps
18
+ from starVLA.dataloader.infoflow_sampler import ValidAnchorManifest
19
+
20
+
21
+ UPSTREAM_COMMIT = "965becf63e9cd5cc339fbf7fde40d1db47efa67a"
22
+ METHOD = "FrameSkip-adapted (Gaussian-TPI fallback)"
23
+
24
+
25
+ def load_upstream_module(name: str, path: Path):
26
+ spec = importlib.util.spec_from_file_location(name, path)
27
+ if spec is None or spec.loader is None:
28
+ raise RuntimeError(f"cannot load upstream module {path}")
29
+ module = importlib.util.module_from_spec(spec)
30
+ sys.modules[name] = module
31
+ spec.loader.exec_module(module)
32
+ return module
33
+
34
+
35
+ def trajectory_paths(root: Path, dataset_name: str, trajectory_id: int) -> tuple[Path, Path]:
36
+ dataset = root / dataset_name
37
+ chunk = trajectory_id // 1000
38
+ parquet_path = dataset / "data" / f"chunk-{chunk:03d}" / f"episode_{trajectory_id:06d}.parquet"
39
+ video_path = (
40
+ dataset
41
+ / "videos"
42
+ / f"chunk-{chunk:03d}"
43
+ / "observation.images.cam_high"
44
+ / f"episode_{trajectory_id:06d}.mp4"
45
+ )
46
+ return parquet_path, video_path
47
+
48
+
49
+ def load_trajectory(
50
+ root: Path,
51
+ dataset_name: str,
52
+ trajectory_id: int,
53
+ max_vac_frames: int,
54
+ ) -> dict:
55
+ parquet_path, video_path = trajectory_paths(root, dataset_name, trajectory_id)
56
+ table = parquet.read_table(parquet_path, columns=["action", "timestamp"])
57
+ actions = np.asarray(table["action"].to_pylist(), dtype=np.float32)
58
+ timestamps = np.asarray(table["timestamp"].to_pylist(), dtype=np.float64).reshape(-1)
59
+ if actions.ndim != 2 or actions.shape[1] != 14:
60
+ raise RuntimeError(f"unexpected RoboTwin action shape {actions.shape}: {parquet_path}")
61
+ if len(actions) != len(timestamps):
62
+ raise RuntimeError(f"action/timestamp length mismatch: {parquet_path}")
63
+
64
+ sample_indices = np.linspace(
65
+ 0, len(timestamps) - 1, min(max_vac_frames, len(timestamps)), dtype=int
66
+ )
67
+ frames = get_frames_by_timestamps(
68
+ video_path.as_posix(),
69
+ timestamps[sample_indices],
70
+ video_backend="pyav",
71
+ video_backend_kwargs={},
72
+ )
73
+ observations = []
74
+ for frame in frames:
75
+ array = np.asarray(frame)
76
+ if array.ndim == 3 and array.shape[0] in (1, 3, 4) and array.shape[-1] not in (1, 3, 4):
77
+ array = np.moveaxis(array, 0, -1)
78
+ observations.append(Image.fromarray(array.astype(np.uint8)))
79
+ return {"observations": observations, "actions": actions}
80
+
81
+
82
+ def main() -> None:
83
+ parser = argparse.ArgumentParser()
84
+ parser.add_argument("--manifest", required=True, type=Path)
85
+ parser.add_argument("--data-root", required=True, type=Path)
86
+ parser.add_argument("--upstream-root", required=True, type=Path)
87
+ parser.add_argument("--visual-checkpoint", required=True, type=Path)
88
+ parser.add_argument("--output", required=True, type=Path)
89
+ parser.add_argument("--device", default="cuda")
90
+ parser.add_argument("--max-trajectories", type=int)
91
+ args = parser.parse_args()
92
+
93
+ candidates = (
94
+ args.upstream_root / "starVLA" / "starVLA" / "frameskip" / "utils",
95
+ args.upstream_root / "starVLA" / "frameskip" / "utils",
96
+ )
97
+ utils = next(
98
+ (path for path in candidates if (path / "importance_metrics.py").is_file()),
99
+ None,
100
+ )
101
+ if utils is None:
102
+ checked = ", ".join(str(path) for path in candidates)
103
+ raise FileNotFoundError(f"FrameSkip utils not found; checked: {checked}")
104
+ importance_module = load_upstream_module("frameskip_upstream_importance", utils / "importance_metrics.py")
105
+ pruner_module = load_upstream_module("frameskip_upstream_pruner", utils / "frame_pruner.py")
106
+ calculator = importance_module.GripperAwareImportanceCalculator(
107
+ alpha=0.6,
108
+ beta=0.2,
109
+ gamma=0.2,
110
+ enable_vac=True,
111
+ visual_encoder_name=None,
112
+ device=args.device,
113
+ gripper_boost=2.0,
114
+ )
115
+ # Match the verified Bridge cache builder: retain upstream 224px VAC
116
+ # preprocessing and interpolate DINOv2 positional embeddings dynamically.
117
+ calculator.visual_encoder = timm.create_model(
118
+ "vit_small_patch14_dinov2.lvd142m",
119
+ pretrained=False,
120
+ num_classes=0,
121
+ checkpoint_path=str(args.visual_checkpoint),
122
+ dynamic_img_size=True,
123
+ ).to(args.device).eval()
124
+ pruner = pruner_module.FramePruner(
125
+ compression_ratios=[0.2],
126
+ preserve_key_stages=True,
127
+ min_frames_per_trajectory=5,
128
+ )
129
+
130
+ manifest = ValidAnchorManifest.load(args.manifest)
131
+ records = []
132
+ for dataset in manifest.datasets:
133
+ for episode in dataset.episodes:
134
+ trajectory_id = int(episode.trajectory_id)
135
+ trajectory = load_trajectory(
136
+ args.data_root, dataset.name, trajectory_id, max_vac_frames=16
137
+ )
138
+ scores = calculator.compute_importance(trajectory)
139
+ result = pruner.prune_trajectory(trajectory, scores, 0.2)
140
+ protected = pruner._detect_key_stages(trajectory, scores)
141
+ retained = set(int(x) for x in result.keep_indices)
142
+ protected_set = set(int(x) for x in protected)
143
+ records.append(
144
+ {
145
+ "dataset_name": dataset.name,
146
+ "trajectory_id": trajectory_id,
147
+ "trajectory_length": int(len(trajectory["actions"])),
148
+ "retention_ratio": 0.2,
149
+ "actual_ratio": float(result.actual_ratio),
150
+ "keep_indices": [int(x) for x in result.keep_indices],
151
+ "importance_scores": [float(x) for x in scores],
152
+ "protected_indices": sorted(protected_set),
153
+ "protected_retained_count": len(protected_set & retained),
154
+ "protected_total_count": len(protected_set),
155
+ "protected_retention_rate": (
156
+ len(protected_set & retained) / len(protected_set)
157
+ if protected_set
158
+ else 1.0
159
+ ),
160
+ "vac_frames": len(trajectory["observations"]),
161
+ }
162
+ )
163
+ print(
164
+ f"cached {dataset.name}/{trajectory_id}: "
165
+ f"{len(result.keep_indices)}/{len(trajectory['actions'])}",
166
+ flush=True,
167
+ )
168
+ if args.max_trajectories is not None and len(records) >= args.max_trajectories:
169
+ break
170
+ if args.max_trajectories is not None and len(records) >= args.max_trajectories:
171
+ break
172
+
173
+ payload = {
174
+ "schema_version": 1,
175
+ "method": METHOD,
176
+ "upstream_commit": UPSTREAM_COMMIT,
177
+ "required_trajectory_count": sum(
178
+ len(dataset.episodes) for dataset in manifest.datasets
179
+ ),
180
+ "retention_ratio": 0.2,
181
+ "importance": {
182
+ "calculator": "GripperAwareImportanceCalculator",
183
+ "avi_weight": 0.6,
184
+ "vac_weight": 0.2,
185
+ "tpi_weight": 0.2,
186
+ "vac_enabled": True,
187
+ "max_vac_frames": 16,
188
+ "visual_encoder": "vit_small_patch14_dinov2.lvd142m",
189
+ "tpi": "Gaussian sigma_squared=0.2 fallback",
190
+ "gripper_boost": 2.0,
191
+ },
192
+ "pruning": {
193
+ "first_last": True,
194
+ "gripper_transition_threshold": 0.1,
195
+ "top_action_change_percentile": 90,
196
+ "min_frames_per_trajectory": 5,
197
+ "mapping": "numpy.searchsorted to the next retained index, clipped at end",
198
+ },
199
+ "schedule": {"pruned_minibatches": 5, "full_minibatches": 1},
200
+ "records": records,
201
+ "source_shards": [],
202
+ }
203
+ args.output.parent.mkdir(parents=True, exist_ok=True)
204
+ temporary = args.output.with_suffix(args.output.suffix + ".tmp")
205
+ temporary.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
206
+ temporary.replace(args.output)
207
+ print(f"wrote {args.output} records={len(records)}")
208
+
209
+
210
+ if __name__ == "__main__":
211
+ main()
external_baselines/frameskip_robotwin_adapted/train_frameskip_robotwin_continuation.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Run the Florence-RoboTwin FrameSkip continuation from the 10K state."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+
11
+ import numpy as np
12
+ from omegaconf import OmegaConf
13
+
14
+
15
+ METHOD = "FrameSkip-adapted (Gaussian-TPI fallback)"
16
+
17
+
18
+ def load_cache(path: str) -> tuple[dict[tuple[str, str], np.ndarray], dict]:
19
+ payload = json.loads(Path(path).read_text(encoding="utf-8"))
20
+ if payload.get("method") != METHOD:
21
+ raise RuntimeError(f"unexpected FrameSkip method: {payload.get('method')!r}")
22
+ if float(payload.get("retention_ratio", -1.0)) != 0.2:
23
+ raise RuntimeError("FrameSkip cache must use retention_ratio=0.2")
24
+
25
+ cache: dict[tuple[str, str], np.ndarray] = {}
26
+ for record in payload["records"]:
27
+ key = (record["dataset_name"], str(record["trajectory_id"]))
28
+ keep = np.asarray(record["keep_indices"], dtype=np.int64)
29
+ if key in cache:
30
+ raise RuntimeError(f"duplicate FrameSkip trajectory: {key}")
31
+ if len(keep) == 0 or np.any(np.diff(keep) <= 0):
32
+ raise RuntimeError(f"invalid retained indices for {key}")
33
+ cache[key] = keep
34
+ return cache, payload
35
+
36
+
37
+ def install_adapter(
38
+ cache_path: str,
39
+ start_global_draw_id: int,
40
+ global_microbatch_size: int,
41
+ ) -> None:
42
+ from starVLA.dataloader.gr00t_lerobot.datasets import LeRobotMixtureDataset
43
+
44
+ cache, cache_payload = load_cache(cache_path)
45
+ original_getitem = LeRobotMixtureDataset.__getitem__
46
+ period = 6 # Five pruned microbatches followed by one full-frame microbatch.
47
+
48
+ def frameskip_getitem(self, index):
49
+ trace = self.sample_trace(index)
50
+ offset = int(trace.global_draw_id) - start_global_draw_id
51
+ if offset < 0:
52
+ raise RuntimeError("FrameSkip draw precedes the 10K continuation boundary")
53
+
54
+ microbatch = offset // global_microbatch_size
55
+ is_pruned = microbatch % period < 5
56
+ logical_index = int(trace.base_index)
57
+ actual_index = logical_index
58
+
59
+ if is_pruned:
60
+ key = (trace.dataset_name, str(trace.trajectory_id))
61
+ try:
62
+ retained = cache[key]
63
+ except KeyError as exc:
64
+ raise RuntimeError(f"FrameSkip cache is missing trajectory {key}") from exc
65
+ position = int(np.searchsorted(retained, logical_index))
66
+ actual_index = int(retained[min(position, len(retained) - 1)])
67
+
68
+ dataset = self.datasets[trace.dataset_index]
69
+ original_get_step_data = dataset.get_step_data
70
+
71
+ def redirected_get_step_data(trajectory_id, base_index):
72
+ if str(trajectory_id) != str(trace.trajectory_id):
73
+ raise RuntimeError("FrameSkip attempted to change trajectory identity")
74
+ if int(base_index) != logical_index:
75
+ raise RuntimeError("unexpected nested dataset read during FrameSkip remapping")
76
+ return original_get_step_data(trajectory_id, actual_index)
77
+
78
+ dataset.get_step_data = redirected_get_step_data
79
+ try:
80
+ sample = original_getitem(self, index)
81
+ finally:
82
+ dataset.get_step_data = original_get_step_data
83
+
84
+ sample["sampling_trace"]["frameskip"] = {
85
+ "method": cache_payload["method"],
86
+ "cache_record_count": len(cache_payload["records"]),
87
+ "is_pruned_minibatch": is_pruned,
88
+ "logical_base_index": logical_index,
89
+ "physical_base_index": actual_index,
90
+ "retention_ratio": 0.2 if is_pruned else 1.0,
91
+ }
92
+ return sample
93
+
94
+ LeRobotMixtureDataset.__getitem__ = frameskip_getitem
95
+
96
+
97
+ def main() -> None:
98
+ parser = argparse.ArgumentParser()
99
+ parser.add_argument("--config_yaml", required=True)
100
+ args, overrides = parser.parse_known_args()
101
+
102
+ install_adapter(
103
+ cache_path=os.environ["FRAMESKIP_CACHE"],
104
+ start_global_draw_id=int(os.environ.get("FRAMESKIP_START_GLOBAL_DRAW_ID", "640000")),
105
+ global_microbatch_size=int(os.environ.get("FRAMESKIP_GLOBAL_MICROBATCH_SIZE", "4")),
106
+ )
107
+
108
+ from starVLA.model.framework.share_tools import apply_config_compat
109
+ from starVLA.training import train_starvla
110
+ from starVLA.training.trainer_utils.trainer_tools import normalize_dotlist_args
111
+
112
+ cfg = OmegaConf.load(args.config_yaml)
113
+ cfg = OmegaConf.merge(cfg, OmegaConf.from_dotlist(normalize_dotlist_args(overrides)))
114
+ cfg = apply_config_compat(cfg, strict=True)
115
+ cfg.config_yaml = args.config_yaml
116
+ train_starvla.main(cfg)
117
+
118
+
119
+ if __name__ == "__main__":
120
+ main()
external_baselines/frameskip_robotwin_adapted/validate_assets.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Validate transferred RoboTwin assets before a smoke or Slurm launch."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ from pathlib import Path
9
+
10
+ from train_frameskip_robotwin_continuation import load_cache
11
+
12
+
13
+ def manifest_support(path: Path) -> set[tuple[str, str]]:
14
+ payload = json.loads(path.read_text(encoding="utf-8"))
15
+ support = set()
16
+ for dataset in payload["datasets"]:
17
+ name = dataset.get("name", dataset.get("dataset_name"))
18
+ for episode in dataset.get("episodes", dataset.get("trajectories", [])):
19
+ trajectory_id = episode.get("trajectory_id", episode.get("id"))
20
+ support.add((name, str(trajectory_id)))
21
+ if not support:
22
+ raise RuntimeError("valid-anchor manifest has no trajectory support")
23
+ return support
24
+
25
+
26
+ def main() -> None:
27
+ parser = argparse.ArgumentParser()
28
+ parser.add_argument("--checkpoint", required=True, type=Path)
29
+ parser.add_argument("--manifest", required=True, type=Path)
30
+ parser.add_argument("--cache", required=True, type=Path)
31
+ args = parser.parse_args()
32
+
33
+ commit_path = args.checkpoint / "checkpoint.json"
34
+ commit = json.loads(commit_path.read_text(encoding="utf-8"))
35
+ if commit.get("schema_version") != 1 or int(commit.get("completed_steps", -1)) != 10000:
36
+ raise RuntimeError("checkpoint commit is not a complete step-10000 state")
37
+ if int(commit.get("world_size", -1)) != 2:
38
+ raise RuntimeError("10K checkpoint world size must be 2")
39
+ inventory = commit.get("files", [])
40
+ missing = [
41
+ entry["name"]
42
+ for entry in inventory
43
+ if not (args.checkpoint / entry["name"]).is_file()
44
+ or (args.checkpoint / entry["name"]).stat().st_size != int(entry["bytes"])
45
+ ]
46
+ if not inventory or missing:
47
+ raise RuntimeError(f"incomplete 10K checkpoint inventory: missing={missing}")
48
+ parent_config = args.checkpoint.parent.parent / "config.full.yaml"
49
+ if not parent_config.is_file():
50
+ raise RuntimeError(f"missing parent config required by exact resume: {parent_config}")
51
+
52
+ cache, payload = load_cache(str(args.cache))
53
+ support = manifest_support(args.manifest)
54
+ cache_support = set(cache)
55
+ if support != cache_support:
56
+ raise RuntimeError(
57
+ "FrameSkip cache support differs from manifest support: "
58
+ f"missing={len(support - cache_support)}, extra={len(cache_support - support)}"
59
+ )
60
+
61
+ print("FRAMESKIP_ROBOTWIN_ASSET_PREFLIGHT=PASS")
62
+ print(f"checkpoint_files={len(inventory)}")
63
+ print(f"trajectory_support={len(support)}")
64
+ print(f"cache_records={len(payload['records'])}")
65
+
66
+
67
+ if __name__ == "__main__":
68
+ main()
external_baselines/keyframe_focused_adapted/build_ape_cache.py ADDED
@@ -0,0 +1,485 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Build APE weights on the frozen Florence-Bridge valid-anchor support."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ from collections import OrderedDict, defaultdict
9
+ from pathlib import Path
10
+
11
+ import numpy as np
12
+ import pyarrow.parquet as parquet
13
+ import torch
14
+ from torch.utils.data import DataLoader, TensorDataset
15
+
16
+ from starVLA.dataloader.infoflow_sampler import LogicalDraw, ValidAnchorManifest
17
+
18
+ from external_baselines.keyframe_focused_adapted.keyframe_cache import (
19
+ ActionCopycat,
20
+ history_target,
21
+ q99_normalize,
22
+ )
23
+
24
+
25
+ def episode_path(data_root: Path, dataset_name: str, trajectory_id: int) -> Path:
26
+ return (
27
+ data_root
28
+ / dataset_name
29
+ / "data"
30
+ / f"chunk-{trajectory_id // 1000:03d}"
31
+ / f"episode_{trajectory_id:06d}.parquet"
32
+ )
33
+
34
+
35
+ def read_actions(
36
+ data_root: Path,
37
+ dataset_name: str,
38
+ trajectory_id: int,
39
+ action_dim: int | None,
40
+ ) -> np.ndarray:
41
+ path = episode_path(data_root, dataset_name, trajectory_id)
42
+ values = parquet.read_table(path, columns=["action"])["action"].to_pylist()
43
+ actions = np.asarray(values, dtype=np.float32)
44
+ if actions.ndim != 2 or (action_dim is not None and actions.shape[1] != action_dim):
45
+ raise RuntimeError(f"unexpected action shape {actions.shape} in {path}")
46
+ return actions
47
+
48
+
49
+ def resolve_statistics_key(
50
+ dataset_name: str,
51
+ statistics_payload: dict,
52
+ explicit_mapping: dict[str, str],
53
+ ) -> str:
54
+ """Resolve Bridge aliases and arbitrary RoboTwin dataset statistics."""
55
+
56
+ aliases = {
57
+ "bridge_orig_lerobot": "oxe_bridge",
58
+ "fractal20220817_data_lerobot": "oxe_rt1",
59
+ }
60
+ candidates = (
61
+ explicit_mapping.get(dataset_name),
62
+ aliases.get(dataset_name),
63
+ dataset_name,
64
+ )
65
+ for candidate in candidates:
66
+ if candidate and candidate in statistics_payload:
67
+ return candidate
68
+ if len(statistics_payload) == 1:
69
+ return next(iter(statistics_payload))
70
+ raise KeyError(
71
+ f"no statistics entry for {dataset_name!r}; provide --statistics-key-map"
72
+ )
73
+
74
+
75
+ def requested_support(manifest, args):
76
+ if args.scope == "full":
77
+ return {
78
+ dataset.name: OrderedDict(
79
+ (int(episode.trajectory_id), tuple(int(x) for x in episode.anchors))
80
+ for episode in dataset.episodes
81
+ )
82
+ for dataset in manifest.datasets
83
+ }
84
+ selected = defaultdict(lambda: defaultdict(set))
85
+ for draw_id in range(args.draw_start, args.draw_start + args.draw_count):
86
+ trace = manifest.sample(args.draw_seed, LogicalDraw(0, draw_id))
87
+ selected[trace.dataset_name][int(trace.trajectory_id)].add(int(trace.base_index))
88
+ return {
89
+ dataset.name: OrderedDict(
90
+ (
91
+ int(episode.trajectory_id),
92
+ tuple(sorted(selected[dataset.name][int(episode.trajectory_id)])),
93
+ )
94
+ for episode in dataset.episodes
95
+ if selected[dataset.name].get(int(episode.trajectory_id))
96
+ )
97
+ for dataset in manifest.datasets
98
+ }
99
+
100
+
101
+ def fitting_support(manifest, max_anchors: int | None):
102
+ result = {}
103
+ for dataset in manifest.datasets:
104
+ episodes = OrderedDict()
105
+ count = 0
106
+ for episode in dataset.episodes:
107
+ anchors = tuple(int(x) for x in episode.anchors if int(x) >= 9)
108
+ if max_anchors is not None:
109
+ anchors = anchors[: max(0, max_anchors - count)]
110
+ if anchors:
111
+ episodes[int(episode.trajectory_id)] = anchors
112
+ count += len(anchors)
113
+ if max_anchors is not None and count >= max_anchors:
114
+ break
115
+ if count == 0:
116
+ raise RuntimeError(f"no copycat fitting anchors for {dataset.name}")
117
+ result[dataset.name] = episodes
118
+ return result
119
+
120
+
121
+ def examples_for_support(
122
+ data_root,
123
+ dataset_name,
124
+ support,
125
+ statistics,
126
+ *,
127
+ action_dim,
128
+ binary_action_indices,
129
+ history=9,
130
+ ):
131
+ inputs, targets = [], []
132
+ for trajectory_id, anchors in support.items():
133
+ actions = q99_normalize(
134
+ read_actions(data_root, dataset_name, trajectory_id, action_dim),
135
+ statistics,
136
+ binary_action_indices=binary_action_indices,
137
+ )
138
+ for anchor in anchors:
139
+ x, y = history_target(actions, anchor, history=history)
140
+ inputs.append(x)
141
+ targets.append(y)
142
+ return np.asarray(inputs, dtype=np.float32), np.asarray(targets, dtype=np.float32)
143
+
144
+
145
+ def train_copycat(inputs, targets, *, action_dim, epochs, device, seed=0):
146
+ generator = torch.Generator().manual_seed(seed)
147
+ permutation = torch.randperm(len(inputs), generator=generator)
148
+ split = int(len(inputs) * 0.8)
149
+ train_indices = permutation[:split]
150
+ val_indices = permutation[split:]
151
+ x = torch.from_numpy(inputs)
152
+ y = torch.from_numpy(targets)
153
+ train = TensorDataset(x[train_indices], y[train_indices])
154
+ val = TensorDataset(x[val_indices], y[val_indices])
155
+ train_loader = DataLoader(
156
+ train, batch_size=512, shuffle=True, generator=generator, num_workers=0
157
+ )
158
+ val_loader = DataLoader(val, batch_size=512, shuffle=False, num_workers=0)
159
+ torch.manual_seed(seed)
160
+ model = ActionCopycat(action_dim=action_dim).to(device)
161
+ optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, betas=(0.9, 0.999))
162
+ history = []
163
+ for epoch in range(1, epochs + 1):
164
+ model.train()
165
+ losses = []
166
+ for batch_x, batch_y in train_loader:
167
+ batch_x = batch_x.to(device)
168
+ batch_y = batch_y.to(device)
169
+ optimizer.zero_grad(set_to_none=True)
170
+ loss = (model(batch_x) - batch_y).square().mean(dim=-1).mean()
171
+ loss.backward()
172
+ optimizer.step()
173
+ losses.append(float(loss.detach().cpu()))
174
+ model.eval()
175
+ val_losses = []
176
+ with torch.no_grad():
177
+ for batch_x, batch_y in val_loader:
178
+ batch_x = batch_x.to(device)
179
+ batch_y = batch_y.to(device)
180
+ loss = (model(batch_x) - batch_y).square().mean(dim=-1)
181
+ val_losses.extend(loss.cpu().tolist())
182
+ history.append(
183
+ {
184
+ "epoch": epoch,
185
+ "train_loss": float(np.mean(losses)),
186
+ "validation_loss": float(np.mean(val_losses)),
187
+ }
188
+ )
189
+ print(json.dumps(history[-1], sort_keys=True), flush=True)
190
+ if epoch % max(1, epochs // 3) == 0:
191
+ for group in optimizer.param_groups:
192
+ group["lr"] *= 0.1
193
+ return model, history
194
+
195
+
196
+ def prediction_errors(model, inputs, targets, device):
197
+ errors = []
198
+ loader = DataLoader(
199
+ TensorDataset(torch.from_numpy(inputs), torch.from_numpy(targets)),
200
+ batch_size=4096,
201
+ shuffle=False,
202
+ num_workers=0,
203
+ )
204
+ model.eval()
205
+ with torch.no_grad():
206
+ for batch_x, batch_y in loader:
207
+ batch_x = batch_x.to(device)
208
+ batch_y = batch_y.to(device)
209
+ errors.append(
210
+ (model(batch_x) - batch_y).square().mean(dim=-1).cpu().numpy()
211
+ )
212
+ return np.concatenate(errors)
213
+
214
+
215
+ def score_support(
216
+ model,
217
+ data_root,
218
+ dataset_name,
219
+ support,
220
+ statistics,
221
+ device,
222
+ *,
223
+ action_dim,
224
+ binary_action_indices,
225
+ ):
226
+ trajectory_ids, offsets, anchors_out, ape_out = [], [0], [], []
227
+ model.eval()
228
+ with torch.no_grad():
229
+ for trajectory_id, anchors in support.items():
230
+ actions = q99_normalize(
231
+ read_actions(data_root, dataset_name, trajectory_id, action_dim),
232
+ statistics,
233
+ binary_action_indices=binary_action_indices,
234
+ )
235
+ batch_x, batch_y = [], []
236
+ for anchor in anchors:
237
+ x, y = history_target(actions, anchor, history=9)
238
+ batch_x.append(x)
239
+ batch_y.append(y)
240
+ x = torch.from_numpy(np.asarray(batch_x, dtype=np.float32)).to(device)
241
+ y = torch.from_numpy(np.asarray(batch_y, dtype=np.float32)).to(device)
242
+ ape = (model(x) - y).square().mean(dim=-1).cpu().numpy()
243
+ trajectory_ids.append(trajectory_id)
244
+ anchors_out.extend(anchors)
245
+ ape_out.extend(ape.tolist())
246
+ offsets.append(len(anchors_out))
247
+ return {
248
+ "trajectory_ids": np.asarray(trajectory_ids, dtype=np.int64),
249
+ "offsets": np.asarray(offsets, dtype=np.int64),
250
+ "anchors": np.asarray(anchors_out, dtype=np.int32),
251
+ "ape": np.asarray(ape_out, dtype=np.float32),
252
+ }
253
+
254
+
255
+ def verify_reference_overlap(reference, dataset_index, scored, weights):
256
+ prefix = f"d{dataset_index}_"
257
+ reference_trajectory_ids = reference[prefix + "trajectory_ids"]
258
+ reference_offsets = reference[prefix + "offsets"]
259
+ reference_anchors = reference[prefix + "anchors"]
260
+ reference_ape = reference[prefix + "ape"]
261
+ reference_weights = reference[prefix + "weights"]
262
+ new_trajectory_positions = {
263
+ int(trajectory_id): position
264
+ for position, trajectory_id in enumerate(scored["trajectory_ids"])
265
+ }
266
+ checked = 0
267
+ max_ape_difference = 0.0
268
+ for reference_position, trajectory_id in enumerate(reference_trajectory_ids):
269
+ new_position = new_trajectory_positions.get(int(trajectory_id))
270
+ if new_position is None:
271
+ raise RuntimeError(
272
+ f"full cache is missing reference trajectory {int(trajectory_id)}"
273
+ )
274
+ reference_slice = slice(
275
+ int(reference_offsets[reference_position]),
276
+ int(reference_offsets[reference_position + 1]),
277
+ )
278
+ new_slice = slice(
279
+ int(scored["offsets"][new_position]),
280
+ int(scored["offsets"][new_position + 1]),
281
+ )
282
+ new_anchors = scored["anchors"][new_slice]
283
+ positions = np.searchsorted(new_anchors, reference_anchors[reference_slice])
284
+ if np.any(positions >= len(new_anchors)) or not np.array_equal(
285
+ new_anchors[positions], reference_anchors[reference_slice]
286
+ ):
287
+ raise RuntimeError(
288
+ f"full cache does not contain the reference anchor support for "
289
+ f"trajectory {int(trajectory_id)}"
290
+ )
291
+ new_ape = scored["ape"][new_slice][positions]
292
+ ape_difference = np.abs(new_ape - reference_ape[reference_slice])
293
+ if len(ape_difference):
294
+ max_ape_difference = max(max_ape_difference, float(ape_difference.max()))
295
+ if not np.array_equal(
296
+ weights[new_slice][positions], reference_weights[reference_slice]
297
+ ):
298
+ raise RuntimeError(
299
+ f"full cache changed reference weights for trajectory "
300
+ f"{int(trajectory_id)}"
301
+ )
302
+ checked += len(positions)
303
+ if max_ape_difference > 1e-6:
304
+ raise RuntimeError(
305
+ f"full cache changed reference APE scores: max_abs_diff={max_ape_difference}"
306
+ )
307
+ return {
308
+ "checked_anchor_count": checked,
309
+ "weights_exact_match": True,
310
+ "max_ape_abs_difference": max_ape_difference,
311
+ }
312
+
313
+
314
+ def main():
315
+ parser = argparse.ArgumentParser()
316
+ parser.add_argument("--manifest", type=Path, required=True)
317
+ parser.add_argument("--data-root", type=Path, required=True)
318
+ parser.add_argument("--statistics", type=Path, required=True)
319
+ parser.add_argument(
320
+ "--statistics-key-map",
321
+ type=Path,
322
+ help="Optional JSON mapping from manifest dataset name to statistics key.",
323
+ )
324
+ parser.add_argument(
325
+ "--action-dim",
326
+ type=int,
327
+ choices=(7, 14),
328
+ help="Expected action dimension; inferred from statistics when omitted.",
329
+ )
330
+ parser.add_argument(
331
+ "--binary-action-indices",
332
+ help=(
333
+ "Comma-separated zero-based binary action indices. Defaults to index 6 "
334
+ "for 7D Bridge and none for 14D RoboTwin absolute qpos."
335
+ ),
336
+ )
337
+ parser.add_argument("--output", type=Path, required=True)
338
+ parser.add_argument("--scope", choices=("full", "draw-range"), default="full")
339
+ parser.add_argument("--draw-seed", type=int, default=117465)
340
+ parser.add_argument("--draw-start", type=int, default=1_280_000)
341
+ parser.add_argument("--draw-count", type=int, default=128)
342
+ parser.add_argument("--fit-max-anchors", type=int)
343
+ parser.add_argument("--epochs", type=int, default=300)
344
+ parser.add_argument("--device", default="cuda")
345
+ parser.add_argument("--reference-cache", type=Path)
346
+ args = parser.parse_args()
347
+
348
+ manifest = ValidAnchorManifest.load(args.manifest)
349
+ statistics_payload = json.loads(args.statistics.read_text(encoding="utf-8"))
350
+ statistics_key_map = (
351
+ json.loads(args.statistics_key_map.read_text(encoding="utf-8"))
352
+ if args.statistics_key_map is not None
353
+ else {}
354
+ )
355
+ reference = None
356
+ reference_metadata = None
357
+ if args.reference_cache is not None:
358
+ if args.scope != "full":
359
+ raise ValueError("--reference-cache requires --scope full")
360
+ reference = np.load(args.reference_cache, allow_pickle=False)
361
+ reference_metadata = json.loads(str(reference["metadata_json"]))
362
+ if int(reference_metadata["copycat_epochs"]) != args.epochs:
363
+ raise RuntimeError("reference cache copycat epoch count does not match")
364
+ scoring = requested_support(manifest, args)
365
+ fitting = fitting_support(manifest, args.fit_max_anchors)
366
+ arrays = {}
367
+ dataset_reports = []
368
+ for dataset_index, dataset in enumerate(manifest.datasets):
369
+ dataset_name = dataset.name
370
+ statistics_key = resolve_statistics_key(
371
+ dataset_name, statistics_payload, statistics_key_map
372
+ )
373
+ action_statistics = statistics_payload[statistics_key]["action"]
374
+ inferred_action_dim = len(action_statistics["q99"])
375
+ action_dim = args.action_dim or inferred_action_dim
376
+ if inferred_action_dim != action_dim:
377
+ raise RuntimeError(
378
+ f"statistics action dimension {inferred_action_dim} != {action_dim} "
379
+ f"for {dataset_name}"
380
+ )
381
+ if args.binary_action_indices is None:
382
+ binary_action_indices = (6,) if action_dim == 7 else ()
383
+ else:
384
+ binary_action_indices = tuple(
385
+ int(value)
386
+ for value in args.binary_action_indices.split(",")
387
+ if value.strip()
388
+ )
389
+ inputs, targets = examples_for_support(
390
+ args.data_root,
391
+ dataset_name,
392
+ fitting[dataset_name],
393
+ action_statistics,
394
+ action_dim=action_dim,
395
+ binary_action_indices=binary_action_indices,
396
+ )
397
+ print(
398
+ f"training copycat for {dataset_name}: {len(inputs)} anchors",
399
+ flush=True,
400
+ )
401
+ model, train_history = train_copycat(
402
+ inputs,
403
+ targets,
404
+ action_dim=action_dim,
405
+ epochs=args.epochs,
406
+ device=torch.device(args.device),
407
+ )
408
+ fitted_ape = prediction_errors(
409
+ model, inputs, targets, torch.device(args.device)
410
+ )
411
+ scored = score_support(
412
+ model,
413
+ args.data_root,
414
+ dataset_name,
415
+ scoring[dataset_name],
416
+ action_statistics,
417
+ torch.device(args.device),
418
+ action_dim=action_dim,
419
+ binary_action_indices=binary_action_indices,
420
+ )
421
+ if reference_metadata is None:
422
+ threshold_values = scored["ape"] if args.scope == "full" else fitted_ape
423
+ threshold = float(np.quantile(threshold_values, 0.9))
424
+ else:
425
+ reference_report = reference_metadata["datasets"][dataset_index]
426
+ if reference_report["name"] != dataset_name:
427
+ raise RuntimeError("reference cache dataset order does not match")
428
+ if int(reference_report["fit_anchor_count"]) != len(inputs):
429
+ raise RuntimeError("reference cache fitting support does not match")
430
+ threshold = float(reference_report["ape_threshold_90th_percentile"])
431
+ weights = np.where(scored["ape"] >= threshold, 5.0, 1.0).astype(np.float32)
432
+ overlap_report = None
433
+ if reference is not None:
434
+ overlap_report = verify_reference_overlap(
435
+ reference, dataset_index, scored, weights
436
+ )
437
+ for key, value in scored.items():
438
+ arrays[f"d{dataset_index}_{key}"] = value
439
+ arrays[f"d{dataset_index}_weights"] = weights
440
+ dataset_reports.append(
441
+ {
442
+ "name": dataset_name,
443
+ "statistics_key": statistics_key,
444
+ "action_dim": action_dim,
445
+ "binary_action_indices": list(binary_action_indices),
446
+ "fit_anchor_count": len(inputs),
447
+ "scored_anchor_count": len(scored["anchors"]),
448
+ "ape_threshold_90th_percentile": threshold,
449
+ "upweighted_count": int((weights > 1).sum()),
450
+ "copycat_training": train_history,
451
+ "reference_overlap": overlap_report,
452
+ }
453
+ )
454
+
455
+ metadata = {
456
+ "schema_version": 1,
457
+ "method": "Keyframe-Focused-adapted",
458
+ "history_length": 9,
459
+ "copycat_hidden_units": [300],
460
+ "copycat_optimizer": {"name": "Adam", "lr": 1e-3, "betas": [0.9, 0.999]},
461
+ "copycat_epochs": args.epochs,
462
+ "ape_metric": "mean squared error over normalized action dimensions",
463
+ "boundary_padding": "repeat earliest physical action",
464
+ "threshold_scope": "within dataset source",
465
+ "threshold_percentile": 90,
466
+ "high_weight": 5.0,
467
+ "low_weight": 1.0,
468
+ "cache_scope": args.scope,
469
+ "threshold_source": (
470
+ "reference_cache" if args.reference_cache is not None else "computed"
471
+ ),
472
+ "dataset_names": [dataset.name for dataset in manifest.datasets],
473
+ "datasets": dataset_reports,
474
+ }
475
+ arrays["metadata_json"] = np.asarray(json.dumps(metadata, sort_keys=True))
476
+ args.output.parent.mkdir(parents=True, exist_ok=True)
477
+ temporary = args.output.with_suffix(args.output.suffix + ".tmp")
478
+ with temporary.open("wb") as destination:
479
+ np.savez_compressed(destination, **arrays)
480
+ temporary.replace(args.output)
481
+ print(f"wrote {args.output}", flush=True)
482
+
483
+
484
+ if __name__ == "__main__":
485
+ main()
external_baselines/keyframe_focused_adapted/configs/keyframe_focused_seed117465_20k_to_20500.yaml ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: keyframe-focused-adapted-seed117465-20k-to20500
2
+ run_root_dir: ${oc.env:OPTR_RUN_ROOT}
3
+ seed: 117465
4
+ wandb_entity: null
5
+ wandb_project: OPTR-Bridge-baselines
6
+ wandb_group: keyframe-focused
7
+ wandb_job_type: train
8
+ wandb_tags:
9
+ - baseline
10
+ - keyframe-focused
11
+ - bridge
12
+ - train
13
+ is_debug: false
14
+ version_id: "0.21"
15
+
16
+ framework:
17
+ name: QwenGR00T
18
+ qwenvl:
19
+ base_vlm: ${oc.env:FLORENCE_MODEL_PATH}
20
+ attn_implementation: flash_attention_2
21
+ vl_hidden_dim: 2048
22
+ dino:
23
+ dino_backbone: dinov2_vits14
24
+ action_model:
25
+ action_model_type: DiT-B
26
+ action_hidden_dim: 1024
27
+ hidden_size: 1024
28
+ add_pos_embed: true
29
+ max_seq_len: 1024
30
+ action_dim: 7
31
+ state_dim: 7
32
+ action_horizon: 16
33
+ future_action_window_size: 15
34
+ past_action_window_size: 0
35
+ repeated_diffusion_steps: 8
36
+ noise_beta_alpha: 1.5
37
+ noise_beta_beta: 1.0
38
+ noise_s: 0.999
39
+ num_timestep_buckets: 1000
40
+ num_inference_timesteps: 4
41
+ num_target_vision_tokens: 32
42
+ diffusion_model_cfg:
43
+ cross_attention_dim: 2048
44
+ dropout: 0.2
45
+ final_dropout: true
46
+ interleave_self_attention: true
47
+ norm_type: ada_norm
48
+ num_layers: 16
49
+ output_dim: 1024
50
+ positional_embeddings: null
51
+
52
+ datasets:
53
+ vla_data:
54
+ dataset_py: lerobot_datasets
55
+ data_root_dir: ${oc.env:BRIDGE_DATA_ROOT}
56
+ data_mix: bridge_rt_infoflow
57
+ action_type: delta_ee
58
+ action_mode: abs
59
+ precomputed_statistics_path: ${oc.env:BRIDGE_STATISTICS_PATH}
60
+ precomputed_statistics_key_map:
61
+ bridge_orig_lerobot: oxe_bridge
62
+ fractal20220817_data_lerobot: oxe_rt1
63
+ CoT_prompt: Your task is {instruction}. To identify the key objects for your task. Locate their bounding boxes in [x1,y1,x2,y2] format.
64
+ per_device_batch_size: 4
65
+ load_all_data_for_training: true
66
+ obs_image_size: [224, 224]
67
+ video_backend: torchvision_av
68
+ num_workers: 4
69
+ balance_dataset_weights: false
70
+ balance_trajectory_weights: false
71
+ traceable_sampling: true
72
+ expected_world_size: 2
73
+ dataset_provenance_path: ${oc.env:BRIDGE_PROVENANCE_PATH}
74
+ dataset_content_fingerprint_path: ${oc.env:BRIDGE_FINGERPRINT_PATH}
75
+ valid_anchor_manifest_path: ${oc.env:BRIDGE_MANIFEST_PATH}
76
+ temporal_proposal_path: null
77
+ transform_rng_seed: 298226
78
+
79
+ trainer:
80
+ use_deepspeed: true
81
+ max_train_steps: 100000
82
+ num_warmup_steps: 5000
83
+ save_interval: null
84
+ save_steps: [20500]
85
+ evaluation_snapshot_interval: null
86
+ eval_interval: 100001
87
+ repeated_diffusion_steps: 4
88
+ pretrained_checkpoint: null
89
+ is_resume: true
90
+ allow_model_only_resume: false
91
+ learning_rate:
92
+ base: 3.0e-05
93
+ qwen_vl_interface: 1.0e-05
94
+ action_model: 1.0e-04
95
+ lr_scheduler_type: cosine_with_min_lr
96
+ scheduler_specific_kwargs:
97
+ min_lr: 5.0e-07
98
+ freeze_modules: ""
99
+ trainable_modules: ""
100
+ frozen_modules_eval: false
101
+ logging_frequency: 50
102
+ gradient_clipping: 1.0
103
+ gradient_accumulation_steps: 8
104
+ save_format: pt
105
+ save_final_checkpoint: false
106
+ optimizer:
107
+ name: AdamW
108
+ betas: [0.9, 0.95]
109
+ eps: 1.0e-08
110
+ weight_decay: 1.0e-08
111
+ resume_accelerate_checkpoint: ${oc.env:COMMON_START_20K}
112
+ branch_parent_seed: 42
113
+ branch_rng_seed: 117465
114
+ model_rng_seed: 588643
115
+ sample_loss_weight_key: keyframe_weight
116
+ stop_after_steps: 20500
117
+
118
+ output_dir: ${oc.env:OPTR_RUN_ROOT}/keyframe-focused-adapted-seed117465-20k-to20500
external_baselines/keyframe_focused_adapted/keyframe_cache.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """APE model and compact cache used by the isolated Keyframe baseline."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+ import torch
11
+ from torch import nn
12
+
13
+
14
+ class ActionCopycat(nn.Module):
15
+ """Official one-hidden-layer action-only copycat architecture."""
16
+
17
+ def __init__(self, action_dim: int = 7, history: int = 9, hidden: int = 300):
18
+ super().__init__()
19
+ self.net = nn.Sequential(
20
+ nn.Linear(action_dim * history, hidden),
21
+ nn.LeakyReLU(0.2, inplace=True),
22
+ nn.Linear(hidden, action_dim),
23
+ )
24
+
25
+ def forward(self, action_history: torch.Tensor) -> torch.Tensor:
26
+ return self.net(action_history)
27
+
28
+
29
+ def q99_normalize(
30
+ actions: np.ndarray,
31
+ statistics: dict,
32
+ *,
33
+ binary_action_indices: tuple[int, ...] = (),
34
+ ) -> np.ndarray:
35
+ """Apply q01/q99 normalization with an explicit binary-action contract.
36
+
37
+ A zero percentile span is not sufficient evidence that an action dimension is
38
+ binary. This matters for RoboTwin absolute qpos, where a joint can be constant
39
+ in a dataset shard while remaining a continuous joint. Such continuous,
40
+ zero-span dimensions are mapped to zero; only explicitly declared indices use
41
+ the binary threshold.
42
+ """
43
+
44
+ actions = np.asarray(actions, dtype=np.float32)
45
+ q01 = np.asarray(statistics["q01"], dtype=np.float32)
46
+ q99 = np.asarray(statistics["q99"], dtype=np.float32)
47
+ if actions.shape[-1] != len(q01) or q01.shape != q99.shape:
48
+ raise ValueError(
49
+ f"action/statistics shape mismatch: actions={actions.shape}, "
50
+ f"q01={q01.shape}, q99={q99.shape}"
51
+ )
52
+ binary = np.zeros(len(q01), dtype=bool)
53
+ for index in binary_action_indices:
54
+ if index < 0 or index >= len(q01):
55
+ raise ValueError(f"binary action index {index} is out of range")
56
+ binary[index] = True
57
+ continuous = ~binary
58
+ scaled = continuous & (q01 != q99)
59
+ constant = continuous & (q01 == q99)
60
+ normalized = np.zeros_like(actions, dtype=np.float32)
61
+ normalized[..., scaled] = (
62
+ 2.0
63
+ * (actions[..., scaled] - q01[scaled])
64
+ / (q99[scaled] - q01[scaled])
65
+ - 1.0
66
+ )
67
+ normalized[..., scaled] = np.clip(
68
+ normalized[..., scaled], -2.2, 2.2
69
+ )
70
+ normalized[..., constant] = 0.0
71
+ normalized[..., binary] = (
72
+ actions[..., binary] > 0.5
73
+ ).astype(np.float32)
74
+ return normalized
75
+
76
+
77
+ def history_target(actions: np.ndarray, anchor: int, history: int = 9):
78
+ """Repeat the earliest physical action at a trajectory boundary."""
79
+
80
+ indices = np.arange(anchor - history, anchor, dtype=np.int64)
81
+ indices = np.clip(indices, 0, len(actions) - 1)
82
+ return actions[indices].reshape(-1), actions[anchor]
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class CacheRecord:
87
+ ape: float
88
+ weight: float
89
+
90
+
91
+ class KeyframeWeightCache:
92
+ """Read a compact NPZ cache without altering sampling."""
93
+
94
+ def __init__(self, path: str | Path):
95
+ self.path = Path(path)
96
+ payload = np.load(self.path, allow_pickle=False)
97
+ self.metadata = json.loads(str(payload["metadata_json"].item()))
98
+ if self.metadata.get("method") != "Keyframe-Focused-adapted":
99
+ raise RuntimeError("unexpected keyframe cache method")
100
+ self._datasets = {}
101
+ for index, name in enumerate(self.metadata["dataset_names"]):
102
+ trajectory_ids = payload[f"d{index}_trajectory_ids"].astype(np.int64)
103
+ offsets = payload[f"d{index}_offsets"].astype(np.int64)
104
+ anchors = payload[f"d{index}_anchors"].astype(np.int64)
105
+ ape = payload[f"d{index}_ape"].astype(np.float32)
106
+ weights = payload[f"d{index}_weights"].astype(np.float32)
107
+ if not (len(anchors) == len(ape) == len(weights)):
108
+ raise RuntimeError(f"cache array lengths differ for {name}")
109
+ if len(offsets) != len(trajectory_ids) + 1 or int(offsets[-1]) != len(anchors):
110
+ raise RuntimeError(f"cache offsets are invalid for {name}")
111
+ if np.any(~np.isfinite(ape)) or np.any(~np.isfinite(weights)):
112
+ raise RuntimeError(f"cache contains non-finite values for {name}")
113
+ if np.any(weights <= 0):
114
+ raise RuntimeError(f"cache contains non-positive weights for {name}")
115
+ self._datasets[name] = {
116
+ "trajectory_to_row": {
117
+ int(trajectory_id): row
118
+ for row, trajectory_id in enumerate(trajectory_ids)
119
+ },
120
+ "offsets": offsets,
121
+ "anchors": anchors,
122
+ "ape": ape,
123
+ "weights": weights,
124
+ }
125
+
126
+ def lookup(self, dataset_name: str, trajectory_id: int, base_index: int) -> CacheRecord:
127
+ try:
128
+ dataset = self._datasets[dataset_name]
129
+ row = dataset["trajectory_to_row"][int(trajectory_id)]
130
+ except KeyError as exc:
131
+ raise KeyError(
132
+ f"keyframe cache has no trajectory {(dataset_name, trajectory_id)}"
133
+ ) from exc
134
+ start, end = dataset["offsets"][row : row + 2]
135
+ local = dataset["anchors"][start:end]
136
+ position = int(np.searchsorted(local, int(base_index)))
137
+ if position >= len(local) or int(local[position]) != int(base_index):
138
+ raise KeyError(
139
+ f"keyframe cache has no anchor {(dataset_name, trajectory_id, base_index)}"
140
+ )
141
+ absolute = int(start + position)
142
+ return CacheRecord(
143
+ ape=float(dataset["ape"][absolute]),
144
+ weight=float(dataset["weights"][absolute]),
145
+ )
external_baselines/keyframe_focused_adapted/train_keyframe_continuation.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Attach frozen APE loss weights to the unchanged Florence-Bridge draw stream."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import os
8
+
9
+ from omegaconf import OmegaConf
10
+
11
+ from external_baselines.keyframe_focused_adapted.keyframe_cache import (
12
+ KeyframeWeightCache,
13
+ )
14
+
15
+
16
+ def install_adapter(cache_path: str) -> KeyframeWeightCache:
17
+ from starVLA.dataloader.gr00t_lerobot.datasets import LeRobotMixtureDataset
18
+ from starVLA.dataloader.infoflow_sampler import ValidAnchorManifest
19
+
20
+ cache = KeyframeWeightCache(cache_path)
21
+ if os.environ.get("KEYFRAME_SMOKE_FROZEN_MANIFEST") == "1":
22
+ original_build_manifest = LeRobotMixtureDataset._build_valid_anchor_manifest
23
+
24
+ def load_frozen_manifest(self):
25
+ path = self.data_cfg.get("valid_anchor_manifest_path")
26
+ if not path:
27
+ return original_build_manifest(self)
28
+ manifest = ValidAnchorManifest.load(path)
29
+ print(
30
+ "Smoke-only frozen manifest fast path: "
31
+ f"{path}",
32
+ flush=True,
33
+ )
34
+ return manifest
35
+
36
+ LeRobotMixtureDataset._build_valid_anchor_manifest = load_frozen_manifest
37
+
38
+ original_getitem = LeRobotMixtureDataset.__getitem__
39
+
40
+ def keyframe_getitem(self, index):
41
+ sample = original_getitem(self, index)
42
+ trace = sample["sampling_trace"]
43
+ record = cache.lookup(
44
+ trace["dataset_name"],
45
+ int(trace["trajectory_id"]),
46
+ int(trace["base_index"]),
47
+ )
48
+ # The logical draw and physical timestep are unchanged; only loss weight is added.
49
+ trace["keyframe_ape"] = record.ape
50
+ trace["keyframe_weight"] = record.weight
51
+ trace["keyframe_cache"] = cache.path.name
52
+ return sample
53
+
54
+ LeRobotMixtureDataset.__getitem__ = keyframe_getitem
55
+ return cache
56
+
57
+
58
+ def main() -> None:
59
+ parser = argparse.ArgumentParser()
60
+ parser.add_argument("--config_yaml", required=True)
61
+ args, clipargs = parser.parse_known_args()
62
+
63
+ cache = install_adapter(os.environ["KEYFRAME_APE_CACHE"])
64
+ print(
65
+ f"Installed Keyframe-Focused-adapted cache {cache.path}",
66
+ flush=True,
67
+ )
68
+
69
+ from starVLA.model.framework.share_tools import apply_config_compat
70
+ from starVLA.training import train_starvla
71
+ from starVLA.training.trainer_utils.trainer_tools import normalize_dotlist_args
72
+
73
+ cfg = OmegaConf.load(args.config_yaml)
74
+ cfg = OmegaConf.merge(cfg, OmegaConf.from_dotlist(normalize_dotlist_args(clipargs)))
75
+ cfg = apply_config_compat(cfg, strict=True)
76
+ if cfg.datasets.vla_data.temporal_proposal_path is not None:
77
+ raise ValueError("Keyframe loss weighting requires the uniform temporal sampler")
78
+ if cfg.trainer.sample_loss_weight_key != "keyframe_weight":
79
+ raise ValueError("trainer.sample_loss_weight_key must be keyframe_weight")
80
+ cfg.config_yaml = args.config_yaml
81
+ train_starvla.main(cfg)
82
+
83
+
84
+ if __name__ == "__main__":
85
+ main()
external_baselines/keyframe_focused_adapted/validate_cache_support.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Verify that a cache covers an unchanged range of uniform logical draws."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ from pathlib import Path
9
+
10
+ import numpy as np
11
+
12
+ from starVLA.dataloader.infoflow_sampler import LogicalDraw, ValidAnchorManifest
13
+
14
+ from external_baselines.keyframe_focused_adapted.keyframe_cache import (
15
+ KeyframeWeightCache,
16
+ )
17
+
18
+
19
+ def main() -> None:
20
+ parser = argparse.ArgumentParser()
21
+ parser.add_argument("--manifest", type=Path, required=True)
22
+ parser.add_argument("--cache", type=Path, required=True)
23
+ parser.add_argument("--seed", type=int)
24
+ parser.add_argument("--draw-start", type=int)
25
+ parser.add_argument("--draw-count", type=int)
26
+ parser.add_argument("--full-support", action="store_true")
27
+ args = parser.parse_args()
28
+
29
+ manifest = ValidAnchorManifest.load(args.manifest)
30
+ cache = KeyframeWeightCache(args.cache)
31
+ if args.full_support:
32
+ arrays = np.load(args.cache, allow_pickle=False)
33
+ metadata = json.loads(str(arrays["metadata_json"]))
34
+ counts: dict[str, int] = {}
35
+ weights: dict[str, int] = {}
36
+ for dataset_index, dataset in enumerate(manifest.datasets):
37
+ prefix = f"d{dataset_index}_"
38
+ trajectory_ids = arrays[prefix + "trajectory_ids"]
39
+ offsets = arrays[prefix + "offsets"]
40
+ anchors = arrays[prefix + "anchors"]
41
+ cached_weights = arrays[prefix + "weights"]
42
+ expected_trajectory_ids = np.asarray(
43
+ [int(episode.trajectory_id) for episode in dataset.episodes],
44
+ dtype=np.int64,
45
+ )
46
+ if not np.array_equal(trajectory_ids, expected_trajectory_ids):
47
+ raise RuntimeError(
48
+ f"cache trajectory support does not match {dataset.name}"
49
+ )
50
+ for position, episode in enumerate(dataset.episodes):
51
+ cached = anchors[int(offsets[position]) : int(offsets[position + 1])]
52
+ expected = np.asarray(episode.anchors, dtype=np.int32)
53
+ if not np.array_equal(cached, expected):
54
+ raise RuntimeError(
55
+ f"cache anchor support does not match "
56
+ f"{dataset.name}/{int(episode.trajectory_id)}"
57
+ )
58
+ counts[dataset.name] = int(len(anchors))
59
+ values, value_counts = np.unique(cached_weights, return_counts=True)
60
+ for value, count in zip(values, value_counts, strict=True):
61
+ label = str(float(value))
62
+ weights[label] = weights.get(label, 0) + int(count)
63
+ print(
64
+ json.dumps(
65
+ {
66
+ "status": "PASS",
67
+ "mode": "full-support",
68
+ "cache": cache.path.name,
69
+ "dataset_anchor_counts": counts,
70
+ "weight_counts": weights,
71
+ },
72
+ sort_keys=True,
73
+ )
74
+ )
75
+ return
76
+ if args.seed is None or args.draw_start is None or args.draw_count is None:
77
+ parser.error("draw-range validation requires --seed, --draw-start, and --draw-count")
78
+ counts: dict[str, int] = {}
79
+ weights: dict[str, int] = {}
80
+ for draw_id in range(args.draw_start, args.draw_start + args.draw_count):
81
+ trace = manifest.sample(args.seed, LogicalDraw(0, draw_id))
82
+ record = cache.lookup(
83
+ trace.dataset_name, trace.trajectory_id, trace.base_index
84
+ )
85
+ counts[trace.dataset_name] = counts.get(trace.dataset_name, 0) + 1
86
+ label = str(record.weight)
87
+ weights[label] = weights.get(label, 0) + 1
88
+
89
+ print(
90
+ json.dumps(
91
+ {
92
+ "status": "PASS",
93
+ "cache": cache.path.name,
94
+ "draw_start": args.draw_start,
95
+ "draw_count": args.draw_count,
96
+ "dataset_counts": counts,
97
+ "weight_counts": weights,
98
+ },
99
+ sort_keys=True,
100
+ )
101
+ )
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()
external_baselines/loss_is_active/train_active_loss_is_continuation.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Run online example-level Loss-IS without changing temporal proposals."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import copy
8
+ import hashlib
9
+ import json
10
+ import math
11
+ import os
12
+ from pathlib import Path
13
+
14
+ import torch
15
+ import torch.distributed as dist
16
+ from omegaconf import OmegaConf
17
+
18
+ from external_baselines.loss_is_adapted.loss_is import sample_with_replacement
19
+
20
+
21
+ def _stable_seed(*parts: object) -> int:
22
+ payload = ":".join(str(part) for part in parts).encode("utf-8")
23
+ return int.from_bytes(hashlib.sha256(payload).digest()[:8], "little") % (2**63 - 1)
24
+
25
+
26
+ class LossISTrainerMixin:
27
+ """Always-on distributed adaptation of the official B-to-b sampler."""
28
+
29
+ def __init__(self, *args, **kwargs):
30
+ dataloader = kwargs.get("vla_train_dataloader")
31
+ if dataloader is None and len(args) >= 3:
32
+ dataloader = args[2]
33
+ self._loss_is_dataset = getattr(dataloader, "dataset", None)
34
+ super().__init__(*args, **kwargs)
35
+
36
+ cfg = self.config.trainer.loss_is
37
+ self._loss_is_factor = int(cfg.candidate_factor)
38
+ self._loss_is_seed = int(cfg.seed)
39
+ if self._loss_is_factor != 3:
40
+ raise ValueError("The locked ICML'18-style baseline uses candidate_factor=3")
41
+ if self._loss_is_dataset is None or not callable(
42
+ getattr(self._loss_is_dataset, "__getitem__", None)
43
+ ):
44
+ raise TypeError("Loss-IS requires random access to the baseline dataset")
45
+ self._loss_is_active = True
46
+ self._loss_is_selection_metrics: list[dict[str, float]] = []
47
+ self._loss_is_last_metrics: dict[str, float] = {}
48
+
49
+ def prepare_training(self) -> None:
50
+ super().prepare_training()
51
+ if not bool(getattr(self.config.trainer, "is_resume", False)):
52
+ return
53
+
54
+ # An external common-start checkpoint is the initial fork into Loss-IS.
55
+ if bool(getattr(self, "_branched_from_external_checkpoint", False)):
56
+ return
57
+
58
+ state_path = Path(self.config.output_dir) / "loss_is_state.json"
59
+ initial_branch_step = int(
60
+ getattr(self.config.trainer.loss_is, "initial_branch_step", -1)
61
+ )
62
+ if not state_path.is_file() and int(self.completed_steps) == initial_branch_step:
63
+ print(
64
+ "Starting always-on Loss-IS from the exact baseline sidecar at "
65
+ f"step {self.completed_steps}",
66
+ flush=True,
67
+ )
68
+ return
69
+ if not state_path.is_file():
70
+ raise FileNotFoundError(f"Loss-IS resume state is missing: {state_path}")
71
+ payload = json.loads(state_path.read_text(encoding="utf-8"))
72
+ if int(payload["completed_optimizer_step"]) != int(self.completed_steps):
73
+ raise RuntimeError("Loss-IS state step does not match the resumed checkpoint")
74
+ if int(payload["candidate_factor"]) != self._loss_is_factor:
75
+ raise RuntimeError("Loss-IS candidate factor differs from the resumed state")
76
+ if payload.get("activation_mode") != "always_on":
77
+ raise RuntimeError("Loss-IS resume state is not always-on")
78
+ print(
79
+ f"Restored always-on Loss-IS state at step {self.completed_steps}",
80
+ flush=True,
81
+ )
82
+
83
+ def _begin_loss_is_update(self) -> None:
84
+ if self._microbatch_index != 0:
85
+ return
86
+ self._loss_is_active = True
87
+ self._loss_is_selection_metrics = []
88
+
89
+ def _extra_candidate_draw_id(self, base_ids: list[int], slot: int) -> int:
90
+ rank = dist.get_rank() if dist.is_initialized() else 0
91
+ low = _stable_seed(
92
+ self._loss_is_seed,
93
+ "candidate",
94
+ self.completed_steps,
95
+ self._microbatch_index,
96
+ rank,
97
+ min(base_ids),
98
+ slot,
99
+ ) & ((1 << 60) - 1)
100
+ return (1 << 61) | low
101
+
102
+ def _build_candidate_pool(self, base_batch: list[dict]) -> list[dict]:
103
+ from starVLA.dataloader.infoflow_sampler import LogicalDraw
104
+
105
+ base_ids = [int(item["sampling_trace"]["global_draw_id"]) for item in base_batch]
106
+ epochs = {int(item["sampling_trace"]["epoch"]) for item in base_batch}
107
+ if len(epochs) != 1:
108
+ raise RuntimeError("A Loss-IS microbatch crossed logical epochs")
109
+ epoch = epochs.pop()
110
+ candidates = list(base_batch)
111
+ extra_count = (self._loss_is_factor - 1) * len(base_batch)
112
+ for slot in range(extra_count):
113
+ draw_id = self._extra_candidate_draw_id(base_ids, slot)
114
+ candidates.append(self._loss_is_dataset[LogicalDraw(epoch, draw_id)])
115
+ candidate_ids = [
116
+ int(item["sampling_trace"]["global_draw_id"]) for item in candidates
117
+ ]
118
+ if len(candidate_ids) != len(set(candidate_ids)):
119
+ raise RuntimeError("Candidate draw identities must be unique before resampling")
120
+ return candidates
121
+
122
+ @torch.no_grad()
123
+ def _score_candidates(self, candidates: list[dict], chunk_size: int) -> torch.Tensor:
124
+ was_training = bool(self.model.training)
125
+ self.model.eval()
126
+ scores = []
127
+ repeat_count = int(self.config.trainer.repeated_diffusion_steps)
128
+ try:
129
+ for chunk_index, start in enumerate(range(0, len(candidates), chunk_size)):
130
+ chunk = candidates[start : start + chunk_size]
131
+ draw_ids = [
132
+ int(item["sampling_trace"]["global_draw_id"]) for item in chunk
133
+ ]
134
+ with torch.autocast("cuda", dtype=torch.bfloat16):
135
+ with self._paired_model_rng(
136
+ draw_ids,
137
+ 1_000_000 + self._microbatch_index * self._loss_is_factor + chunk_index,
138
+ ) as rng_seeds:
139
+ output = self.model.forward(
140
+ chunk,
141
+ return_per_sample_loss=True,
142
+ flow_noise_seed=(
143
+ None if rng_seeds is None else rng_seeds["flow_noise"]
144
+ ),
145
+ return_flow_noise_time=True,
146
+ )
147
+ chunk_scores = output["action_loss"].detach().to(
148
+ device="cpu", dtype=torch.float64
149
+ )
150
+ if chunk_scores.shape != (len(chunk),):
151
+ raise RuntimeError("Candidate scoring did not return one loss per example")
152
+ noise = output["flow_noise"].detach().cpu()
153
+ time = output["flow_time"].detach().cpu()
154
+ expected_noise_shape = (
155
+ repeat_count * len(chunk),
156
+ *tuple(noise.shape[1:]),
157
+ )
158
+ if tuple(noise.shape) != expected_noise_shape:
159
+ raise RuntimeError("Candidate scoring returned an invalid flow-noise shape")
160
+ if tuple(time.shape) != (repeat_count * len(chunk),):
161
+ raise RuntimeError("Candidate scoring returned an invalid flow-time shape")
162
+ noise = noise.reshape(repeat_count, len(chunk), *noise.shape[1:])
163
+ time = time.reshape(repeat_count, len(chunk))
164
+ for local_index, item in enumerate(chunk):
165
+ # Reuse the scored Flow-Matching realization during backward.
166
+ item["loss_is_flow_noise"] = noise[:, local_index].clone()
167
+ item["loss_is_flow_time"] = time[:, local_index].clone()
168
+ scores.append(chunk_scores)
169
+ finally:
170
+ if was_training:
171
+ self.model.train()
172
+ self._enforce_frozen_module_eval()
173
+ result = torch.cat(scores)
174
+ if not torch.isfinite(result).all() or torch.any(result <= 0):
175
+ raise FloatingPointError("Candidate losses must be finite and strictly positive")
176
+ return result
177
+
178
+ def _selection_generator(self, base_ids: list[int]) -> torch.Generator:
179
+ rank = dist.get_rank() if dist.is_initialized() else 0
180
+ generator = torch.Generator(device="cpu")
181
+ generator.manual_seed(
182
+ _stable_seed(
183
+ self._loss_is_seed,
184
+ "selection",
185
+ self.completed_steps,
186
+ self._microbatch_index,
187
+ rank,
188
+ min(base_ids),
189
+ )
190
+ )
191
+ return generator
192
+
193
+ def _get_next_batch(self):
194
+ base_batch = super()._get_next_batch()
195
+ self._begin_loss_is_update()
196
+ base_ids = [int(item["sampling_trace"]["global_draw_id"]) for item in base_batch]
197
+
198
+ candidates = self._build_candidate_pool(base_batch)
199
+ scores = self._score_candidates(candidates, chunk_size=len(base_batch))
200
+ selection = sample_with_replacement(
201
+ scores,
202
+ len(base_batch),
203
+ generator=self._selection_generator(base_ids),
204
+ )
205
+ selected = []
206
+ for occurrence_id, selected_index, weight, probability in zip(
207
+ base_ids,
208
+ selection.indices.tolist(),
209
+ selection.importance_weights.tolist(),
210
+ selection.selected_probabilities.tolist(),
211
+ ):
212
+ item = copy.deepcopy(candidates[selected_index])
213
+ trace = item["sampling_trace"]
214
+ trace["loss_is_candidate_global_draw_id"] = int(trace["global_draw_id"])
215
+ trace["loss_is_occurrence_id"] = int(occurrence_id)
216
+ trace["loss_is_importance_weight"] = float(weight)
217
+ trace["loss_is_probability"] = float(probability)
218
+ trace["loss_is_candidate_score"] = float(scores[selected_index])
219
+ trace["loss_is_active"] = True
220
+ selected.append(item)
221
+ weights = selection.importance_weights
222
+ self._loss_is_selection_metrics.append(
223
+ {
224
+ "candidate_loss_mean": float(scores.mean()),
225
+ "candidate_loss_max": float(scores.max()),
226
+ "weight_mean": float(weights.mean()),
227
+ "weight_max": float(weights.max()),
228
+ "ess": float(weights.sum().square() / weights.square().sum()),
229
+ }
230
+ )
231
+ return selected
232
+
233
+ def _after_per_sample_training_loss(
234
+ self,
235
+ batch_vla,
236
+ per_sample_loss,
237
+ *,
238
+ did_optimizer_step: bool,
239
+ microbatch_index: int,
240
+ ) -> None:
241
+ if per_sample_loss is None:
242
+ raise RuntimeError("Loss-IS requires per-sample training loss")
243
+ if not did_optimizer_step:
244
+ return
245
+
246
+ metrics = {
247
+ "loss_is/active": 1.0,
248
+ "loss_is/candidate_factor": float(self._loss_is_factor),
249
+ }
250
+ if self._loss_is_selection_metrics:
251
+ for key in self._loss_is_selection_metrics[0]:
252
+ metrics[f"loss_is/{key}"] = sum(
253
+ item[key] for item in self._loss_is_selection_metrics
254
+ ) / len(self._loss_is_selection_metrics)
255
+ self._loss_is_last_metrics = metrics
256
+
257
+ if self.accelerator.is_main_process:
258
+ state_path = Path(self.config.output_dir) / "loss_is_state.json"
259
+ temporary = state_path.with_suffix(".json.tmp")
260
+ payload = {
261
+ "schema_version": 1,
262
+ "completed_optimizer_step": int(self.completed_steps + 1),
263
+ "activation_mode": "always_on",
264
+ "active_for_completed_step": True,
265
+ "candidate_factor": self._loss_is_factor,
266
+ "selection_scope": "per-rank per-microbatch; globally averaged unbiased estimator",
267
+ "metrics": metrics,
268
+ }
269
+ temporary.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
270
+ os.replace(temporary, state_path)
271
+
272
+ def _train_step(self, batch_vla, batch_vlm=None):
273
+ metrics = super()._train_step(batch_vla, batch_vlm=batch_vlm)
274
+ metrics.update(self._loss_is_last_metrics)
275
+ return metrics
276
+
277
+
278
+ def install_smoke_manifest_fast_path() -> None:
279
+ if os.environ.get("LOSS_IS_SMOKE_FROZEN_MANIFEST") != "1":
280
+ return
281
+ from starVLA.dataloader.gr00t_lerobot.datasets import LeRobotMixtureDataset
282
+ from starVLA.dataloader.infoflow_sampler import ValidAnchorManifest
283
+
284
+ def load_frozen_manifest(self):
285
+ path = self.data_cfg.get("valid_anchor_manifest_path")
286
+ if not path:
287
+ raise ValueError("Smoke fast path requires a frozen valid-anchor manifest")
288
+ manifest = ValidAnchorManifest.load(path)
289
+ print(
290
+ f"Loss-IS smoke frozen manifest: {path} sha256={manifest.sha256}",
291
+ flush=True,
292
+ )
293
+ return manifest
294
+
295
+ LeRobotMixtureDataset._build_valid_anchor_manifest = load_frozen_manifest
296
+
297
+
298
+ def main() -> None:
299
+ parser = argparse.ArgumentParser()
300
+ parser.add_argument("--config_yaml", required=True)
301
+ args, clipargs = parser.parse_known_args()
302
+
303
+ install_smoke_manifest_fast_path()
304
+
305
+ from starVLA.model.framework.share_tools import apply_config_compat
306
+ from starVLA.training import train_starvla
307
+ from starVLA.training.trainer_utils.trainer_tools import normalize_dotlist_args
308
+
309
+ cfg = OmegaConf.load(args.config_yaml)
310
+ cfg = OmegaConf.merge(cfg, OmegaConf.from_dotlist(normalize_dotlist_args(clipargs)))
311
+ cfg = apply_config_compat(cfg, strict=True)
312
+ if cfg.datasets.vla_data.temporal_proposal_path is not None:
313
+ raise ValueError("Loss-IS uses the baseline distribution, not a temporal proposal")
314
+ if cfg.trainer.sample_loss_weight_key != "loss_is_importance_weight":
315
+ raise ValueError("sample_loss_weight_key must be loss_is_importance_weight")
316
+ if cfg.trainer.model_rng_trace_key != "loss_is_occurrence_id":
317
+ raise ValueError("model_rng_trace_key must be loss_is_occurrence_id")
318
+ cfg.config_yaml = args.config_yaml
319
+
320
+ base_trainer = train_starvla.VLATrainer
321
+
322
+ class LossISTrainer(LossISTrainerMixin, base_trainer):
323
+ pass
324
+
325
+ train_starvla.VLATrainer = LossISTrainer
326
+ train_starvla.main(cfg)
327
+
328
+
329
+ if __name__ == "__main__":
330
+ main()
external_baselines/loss_is_adapted/configs/loss_is_seed117465_20k_to_20500.yaml ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: loss-is-adapted-seed117465-20k-to20500
2
+ run_root_dir: ${oc.env:OPTR_RUN_ROOT}
3
+ seed: 117465
4
+ wandb_entity: null
5
+ wandb_project: OPTR-Bridge-baselines
6
+ wandb_group: loss-is
7
+ wandb_job_type: train
8
+ wandb_tags:
9
+ - baseline
10
+ - loss-is
11
+ - bridge
12
+ - train
13
+ is_debug: false
14
+ version_id: "0.21"
15
+
16
+ framework:
17
+ name: QwenGR00T
18
+ qwenvl:
19
+ base_vlm: ${oc.env:FLORENCE_MODEL_PATH}
20
+ attn_implementation: flash_attention_2
21
+ vl_hidden_dim: 2048
22
+ dino:
23
+ dino_backbone: dinov2_vits14
24
+ action_model:
25
+ action_model_type: DiT-B
26
+ action_hidden_dim: 1024
27
+ hidden_size: 1024
28
+ add_pos_embed: true
29
+ max_seq_len: 1024
30
+ action_dim: 7
31
+ state_dim: 7
32
+ action_horizon: 16
33
+ future_action_window_size: 15
34
+ past_action_window_size: 0
35
+ repeated_diffusion_steps: 8
36
+ noise_beta_alpha: 1.5
37
+ noise_beta_beta: 1.0
38
+ noise_s: 0.999
39
+ num_timestep_buckets: 1000
40
+ num_inference_timesteps: 4
41
+ num_target_vision_tokens: 32
42
+ diffusion_model_cfg:
43
+ cross_attention_dim: 2048
44
+ dropout: 0.2
45
+ final_dropout: true
46
+ interleave_self_attention: true
47
+ norm_type: ada_norm
48
+ num_layers: 16
49
+ output_dim: 1024
50
+ positional_embeddings: null
51
+
52
+ datasets:
53
+ vla_data:
54
+ dataset_py: lerobot_datasets
55
+ data_root_dir: ${oc.env:BRIDGE_DATA_ROOT}
56
+ data_mix: bridge_rt_infoflow
57
+ action_type: delta_ee
58
+ action_mode: abs
59
+ precomputed_statistics_path: ${oc.env:BRIDGE_STATISTICS_PATH}
60
+ precomputed_statistics_key_map:
61
+ bridge_orig_lerobot: oxe_bridge
62
+ fractal20220817_data_lerobot: oxe_rt1
63
+ CoT_prompt: Your task is {instruction}. To identify the key objects for your task. Locate their bounding boxes in [x1,y1,x2,y2] format.
64
+ per_device_batch_size: 4
65
+ load_all_data_for_training: true
66
+ obs_image_size: [224, 224]
67
+ video_backend: torchvision_av
68
+ num_workers: 4
69
+ balance_dataset_weights: false
70
+ balance_trajectory_weights: false
71
+ traceable_sampling: true
72
+ expected_world_size: 2
73
+ dataset_provenance_path: ${oc.env:BRIDGE_PROVENANCE_PATH}
74
+ dataset_content_fingerprint_path: ${oc.env:BRIDGE_FINGERPRINT_PATH}
75
+ valid_anchor_manifest_path: ${oc.env:BRIDGE_MANIFEST_PATH}
76
+ temporal_proposal_path: null
77
+ transform_rng_seed: 298226
78
+
79
+ trainer:
80
+ use_deepspeed: true
81
+ max_train_steps: 100000
82
+ num_warmup_steps: 5000
83
+ save_interval: null
84
+ save_steps: [20500]
85
+ evaluation_snapshot_interval: null
86
+ eval_interval: 100001
87
+ repeated_diffusion_steps: 4
88
+ pretrained_checkpoint: null
89
+ is_resume: true
90
+ allow_model_only_resume: false
91
+ learning_rate:
92
+ base: 3.0e-05
93
+ qwen_vl_interface: 1.0e-05
94
+ action_model: 1.0e-04
95
+ lr_scheduler_type: cosine_with_min_lr
96
+ scheduler_specific_kwargs:
97
+ min_lr: 5.0e-07
98
+ freeze_modules: ""
99
+ trainable_modules: ""
100
+ frozen_modules_eval: false
101
+ logging_frequency: 50
102
+ gradient_clipping: 1.0
103
+ gradient_accumulation_steps: 8
104
+ save_format: pt
105
+ save_final_checkpoint: false
106
+ optimizer:
107
+ name: AdamW
108
+ betas: [0.9, 0.95]
109
+ eps: 1.0e-08
110
+ weight_decay: 1.0e-08
111
+ resume_accelerate_checkpoint: ${oc.env:COMMON_START_20K}
112
+ branch_parent_seed: 42
113
+ branch_rng_seed: 117465
114
+ model_rng_seed: 588643
115
+ model_rng_trace_key: loss_is_occurrence_id
116
+ sample_loss_weight_key: loss_is_importance_weight
117
+ stop_after_steps: 20500
118
+ loss_is:
119
+ candidate_factor: 3
120
+ seed: 117465
121
+
122
+ output_dir: ${oc.env:OPTR_RUN_ROOT}/loss-is-adapted-seed117465-20k-to20500
external_baselines/loss_is_adapted/loss_is.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ICML'18-style online loss importance-sampling primitives."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ import torch
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class LossISSelection:
12
+ indices: torch.Tensor
13
+ probabilities: torch.Tensor
14
+ selected_probabilities: torch.Tensor
15
+ importance_weights: torch.Tensor
16
+
17
+
18
+ def sample_with_replacement(
19
+ scores: torch.Tensor,
20
+ batch_size: int,
21
+ *,
22
+ generator: torch.Generator,
23
+ ) -> LossISSelection:
24
+ """Select ``b`` of ``B`` candidates and return exact ``1/(B g_i)`` weights."""
25
+
26
+ scores64 = torch.as_tensor(scores, dtype=torch.float64).flatten().cpu()
27
+ if scores64.numel() <= batch_size:
28
+ raise ValueError("candidate pool must be larger than the training batch")
29
+ if batch_size <= 0:
30
+ raise ValueError("batch_size must be positive")
31
+ if not torch.isfinite(scores64).all() or torch.any(scores64 <= 0):
32
+ raise ValueError("loss scores must be finite and strictly positive")
33
+ probability = scores64 / scores64.sum()
34
+ indices = torch.multinomial(
35
+ probability,
36
+ batch_size,
37
+ replacement=True,
38
+ generator=generator,
39
+ )
40
+ selected_probability = probability[indices]
41
+ weights = 1.0 / (scores64.numel() * selected_probability)
42
+ return LossISSelection(
43
+ indices=indices,
44
+ probabilities=probability,
45
+ selected_probabilities=selected_probability,
46
+ importance_weights=weights,
47
+ )
48
+
external_baselines/loss_is_adapted/train_loss_is_continuation.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Run online example-level Loss-IS without changing temporal proposals."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import copy
8
+ import hashlib
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+
13
+ import torch
14
+ import torch.distributed as dist
15
+ from omegaconf import OmegaConf
16
+
17
+ from external_baselines.loss_is_adapted.loss_is import sample_with_replacement
18
+
19
+
20
+ def _stable_seed(*parts: object) -> int:
21
+ payload = ":".join(str(part) for part in parts).encode("utf-8")
22
+ return int.from_bytes(hashlib.sha256(payload).digest()[:8], "little") % (2**63 - 1)
23
+
24
+
25
+ class LossISTrainerMixin:
26
+ """Always-on distributed adaptation of the official B-to-b sampler."""
27
+
28
+ def __init__(self, *args, **kwargs):
29
+ dataloader = kwargs.get("vla_train_dataloader")
30
+ if dataloader is None and len(args) >= 3:
31
+ dataloader = args[2]
32
+ self._loss_is_dataset = getattr(dataloader, "dataset", None)
33
+ super().__init__(*args, **kwargs)
34
+
35
+ cfg = self.config.trainer.loss_is
36
+ self._loss_is_factor = int(cfg.candidate_factor)
37
+ self._loss_is_seed = int(cfg.seed)
38
+ if self._loss_is_factor != 3:
39
+ raise ValueError("The locked ICML'18-style baseline uses candidate_factor=3")
40
+ if self._loss_is_dataset is None or not callable(
41
+ getattr(self._loss_is_dataset, "__getitem__", None)
42
+ ):
43
+ raise TypeError("Loss-IS requires random access to the baseline dataset")
44
+ self._loss_is_active = True
45
+ self._loss_is_selection_metrics: list[dict[str, float]] = []
46
+ self._loss_is_last_metrics: dict[str, float] = {}
47
+
48
+ def _begin_loss_is_update(self) -> None:
49
+ if self._microbatch_index != 0:
50
+ return
51
+ self._loss_is_active = True
52
+ self._loss_is_selection_metrics = []
53
+
54
+ def _extra_candidate_draw_id(self, base_ids: list[int], slot: int) -> int:
55
+ rank = dist.get_rank() if dist.is_initialized() else 0
56
+ low = _stable_seed(
57
+ self._loss_is_seed,
58
+ "candidate",
59
+ self.completed_steps,
60
+ self._microbatch_index,
61
+ rank,
62
+ min(base_ids),
63
+ slot,
64
+ ) & ((1 << 60) - 1)
65
+ return (1 << 61) | low
66
+
67
+ def _build_candidate_pool(self, base_batch: list[dict]) -> list[dict]:
68
+ from starVLA.dataloader.infoflow_sampler import LogicalDraw
69
+
70
+ base_ids = [int(item["sampling_trace"]["global_draw_id"]) for item in base_batch]
71
+ epochs = {int(item["sampling_trace"]["epoch"]) for item in base_batch}
72
+ if len(epochs) != 1:
73
+ raise RuntimeError("A Loss-IS microbatch crossed logical epochs")
74
+ epoch = epochs.pop()
75
+ candidates = list(base_batch)
76
+ extra_count = (self._loss_is_factor - 1) * len(base_batch)
77
+ for slot in range(extra_count):
78
+ draw_id = self._extra_candidate_draw_id(base_ids, slot)
79
+ candidates.append(self._loss_is_dataset[LogicalDraw(epoch, draw_id)])
80
+ candidate_ids = [
81
+ int(item["sampling_trace"]["global_draw_id"]) for item in candidates
82
+ ]
83
+ if len(candidate_ids) != len(set(candidate_ids)):
84
+ raise RuntimeError("Candidate draw identities must be unique before resampling")
85
+ return candidates
86
+
87
+ @torch.no_grad()
88
+ def _score_candidates(self, candidates: list[dict], chunk_size: int) -> torch.Tensor:
89
+ was_training = bool(self.model.training)
90
+ self.model.eval()
91
+ scores = []
92
+ repeat_count = int(self.config.trainer.repeated_diffusion_steps)
93
+ try:
94
+ for chunk_index, start in enumerate(range(0, len(candidates), chunk_size)):
95
+ chunk = candidates[start : start + chunk_size]
96
+ draw_ids = [
97
+ int(item["sampling_trace"]["global_draw_id"]) for item in chunk
98
+ ]
99
+ with torch.autocast("cuda", dtype=torch.bfloat16):
100
+ with self._paired_model_rng(
101
+ draw_ids,
102
+ 1_000_000 + self._microbatch_index * self._loss_is_factor + chunk_index,
103
+ ) as rng_seeds:
104
+ output = self.model.forward(
105
+ chunk,
106
+ return_per_sample_loss=True,
107
+ flow_noise_seed=rng_seeds["flow_noise"],
108
+ return_flow_noise_time=True,
109
+ )
110
+ chunk_scores = output["action_loss"].detach().to(
111
+ device="cpu", dtype=torch.float64
112
+ )
113
+ if chunk_scores.shape != (len(chunk),):
114
+ raise RuntimeError("Candidate scoring did not return one loss per example")
115
+ noise = output["flow_noise"].detach().cpu()
116
+ time = output["flow_time"].detach().cpu()
117
+ expected_noise_shape = (
118
+ repeat_count * len(chunk),
119
+ *tuple(noise.shape[1:]),
120
+ )
121
+ if tuple(noise.shape) != expected_noise_shape:
122
+ raise RuntimeError("Candidate scoring returned an invalid flow-noise shape")
123
+ if tuple(time.shape) != (repeat_count * len(chunk),):
124
+ raise RuntimeError("Candidate scoring returned an invalid flow-time shape")
125
+ noise = noise.reshape(repeat_count, len(chunk), *noise.shape[1:])
126
+ time = time.reshape(repeat_count, len(chunk))
127
+ for local_index, item in enumerate(chunk):
128
+ # Reuse the scored Flow-Matching realization during backward.
129
+ item["loss_is_flow_noise"] = noise[:, local_index].clone()
130
+ item["loss_is_flow_time"] = time[:, local_index].clone()
131
+ scores.append(chunk_scores)
132
+ finally:
133
+ if was_training:
134
+ self.model.train()
135
+ self._enforce_frozen_module_eval()
136
+ result = torch.cat(scores)
137
+ if not torch.isfinite(result).all() or torch.any(result <= 0):
138
+ raise FloatingPointError("Candidate losses must be finite and strictly positive")
139
+ return result
140
+
141
+ def _selection_generator(self, base_ids: list[int]) -> torch.Generator:
142
+ rank = dist.get_rank() if dist.is_initialized() else 0
143
+ generator = torch.Generator(device="cpu")
144
+ generator.manual_seed(
145
+ _stable_seed(
146
+ self._loss_is_seed,
147
+ "selection",
148
+ self.completed_steps,
149
+ self._microbatch_index,
150
+ rank,
151
+ min(base_ids),
152
+ )
153
+ )
154
+ return generator
155
+
156
+ def _get_next_batch(self):
157
+ base_batch = super()._get_next_batch()
158
+ self._begin_loss_is_update()
159
+ base_ids = [int(item["sampling_trace"]["global_draw_id"]) for item in base_batch]
160
+
161
+ candidates = self._build_candidate_pool(base_batch)
162
+ scores = self._score_candidates(candidates, chunk_size=len(base_batch))
163
+ selection = sample_with_replacement(
164
+ scores,
165
+ len(base_batch),
166
+ generator=self._selection_generator(base_ids),
167
+ )
168
+ selected = []
169
+ for occurrence_id, selected_index, weight, probability in zip(
170
+ base_ids,
171
+ selection.indices.tolist(),
172
+ selection.importance_weights.tolist(),
173
+ selection.selected_probabilities.tolist(),
174
+ ):
175
+ item = copy.deepcopy(candidates[selected_index])
176
+ trace = item["sampling_trace"]
177
+ trace["loss_is_candidate_global_draw_id"] = int(trace["global_draw_id"])
178
+ trace["loss_is_occurrence_id"] = int(occurrence_id)
179
+ trace["loss_is_importance_weight"] = float(weight)
180
+ trace["loss_is_probability"] = float(probability)
181
+ trace["loss_is_candidate_score"] = float(scores[selected_index])
182
+ trace["loss_is_active"] = True
183
+ selected.append(item)
184
+ weights = selection.importance_weights
185
+ self._loss_is_selection_metrics.append(
186
+ {
187
+ "candidate_loss_mean": float(scores.mean()),
188
+ "candidate_loss_max": float(scores.max()),
189
+ "weight_mean": float(weights.mean()),
190
+ "weight_max": float(weights.max()),
191
+ "ess": float(weights.sum().square() / weights.square().sum()),
192
+ }
193
+ )
194
+ return selected
195
+
196
+ def _after_per_sample_training_loss(
197
+ self,
198
+ batch_vla,
199
+ per_sample_loss,
200
+ *,
201
+ did_optimizer_step: bool,
202
+ microbatch_index: int,
203
+ ) -> None:
204
+ if per_sample_loss is None:
205
+ raise RuntimeError("Loss-IS requires per-sample training loss")
206
+ if not did_optimizer_step:
207
+ return
208
+
209
+ metrics = {
210
+ "loss_is/active": 1.0,
211
+ "loss_is/candidate_factor": float(self._loss_is_factor),
212
+ }
213
+ if self._loss_is_selection_metrics:
214
+ for key in self._loss_is_selection_metrics[0]:
215
+ metrics[f"loss_is/{key}"] = sum(
216
+ item[key] for item in self._loss_is_selection_metrics
217
+ ) / len(self._loss_is_selection_metrics)
218
+ self._loss_is_last_metrics = metrics
219
+
220
+ if self.accelerator.is_main_process:
221
+ state_path = Path(self.config.output_dir) / "loss_is_state.json"
222
+ temporary = state_path.with_suffix(".json.tmp")
223
+ payload = {
224
+ "schema_version": 1,
225
+ "completed_optimizer_step": int(self.completed_steps + 1),
226
+ "activation_mode": "always_on",
227
+ "active_for_completed_step": True,
228
+ "candidate_factor": self._loss_is_factor,
229
+ "selection_scope": "per-rank per-microbatch; globally averaged unbiased estimator",
230
+ "metrics": metrics,
231
+ }
232
+ temporary.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
233
+ os.replace(temporary, state_path)
234
+
235
+ def _train_step(self, batch_vla, batch_vlm=None):
236
+ metrics = super()._train_step(batch_vla, batch_vlm=batch_vlm)
237
+ metrics.update(self._loss_is_last_metrics)
238
+ return metrics
239
+
240
+
241
+ def install_smoke_manifest_fast_path() -> None:
242
+ if os.environ.get("LOSS_IS_SMOKE_FROZEN_MANIFEST") != "1":
243
+ return
244
+ from starVLA.dataloader.gr00t_lerobot.datasets import LeRobotMixtureDataset
245
+ from starVLA.dataloader.infoflow_sampler import ValidAnchorManifest
246
+
247
+ def load_frozen_manifest(self):
248
+ path = self.data_cfg.get("valid_anchor_manifest_path")
249
+ if not path:
250
+ raise ValueError("Smoke fast path requires a frozen valid-anchor manifest")
251
+ manifest = ValidAnchorManifest.load(path)
252
+ print(
253
+ f"Loss-IS smoke frozen manifest: {path}",
254
+ flush=True,
255
+ )
256
+ return manifest
257
+
258
+ LeRobotMixtureDataset._build_valid_anchor_manifest = load_frozen_manifest
259
+
260
+
261
+ def main() -> None:
262
+ parser = argparse.ArgumentParser()
263
+ parser.add_argument("--config_yaml", required=True)
264
+ args, clipargs = parser.parse_known_args()
265
+
266
+ install_smoke_manifest_fast_path()
267
+
268
+ from starVLA.model.framework.share_tools import apply_config_compat
269
+ from starVLA.training import train_starvla
270
+ from starVLA.training.trainer_utils.trainer_tools import normalize_dotlist_args
271
+
272
+ cfg = OmegaConf.load(args.config_yaml)
273
+ cfg = OmegaConf.merge(cfg, OmegaConf.from_dotlist(normalize_dotlist_args(clipargs)))
274
+ cfg = apply_config_compat(cfg, strict=True)
275
+ if cfg.datasets.vla_data.temporal_proposal_path is not None:
276
+ raise ValueError("Loss-IS uses the baseline distribution, not a temporal proposal")
277
+ if cfg.trainer.sample_loss_weight_key != "loss_is_importance_weight":
278
+ raise ValueError("sample_loss_weight_key must be loss_is_importance_weight")
279
+ if cfg.trainer.model_rng_trace_key != "loss_is_occurrence_id":
280
+ raise ValueError("model_rng_trace_key must be loss_is_occurrence_id")
281
+ cfg.config_yaml = args.config_yaml
282
+
283
+ base_trainer = train_starvla.VLATrainer
284
+
285
+ class LossISTrainer(LossISTrainerMixin, base_trainer):
286
+ pass
287
+
288
+ train_starvla.VLATrainer = LossISTrainer
289
+ train_starvla.main(cfg)
290
+
291
+
292
+ if __name__ == "__main__":
293
+ main()
external_baselines/qwen_adapted/train_qwen_frameskip_continuation.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Thin runtime adapter for the locked Qwen-Bridge FrameSkip continuation."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+
11
+ import numpy as np
12
+ from omegaconf import OmegaConf
13
+
14
+
15
+ def load_cache(path: str) -> tuple[dict[tuple[str, str], np.ndarray], dict]:
16
+ payload = json.loads(Path(path).read_text(encoding="utf-8"))
17
+ if payload.get("method") != "FrameSkip-adapted (Gaussian-TPI fallback)":
18
+ raise RuntimeError("unexpected FrameSkip cache method")
19
+ cache = {}
20
+ for record in payload["records"]:
21
+ key = (record["dataset_name"], str(record["trajectory_id"]))
22
+ keep = np.asarray(record["keep_indices"], dtype=np.int64)
23
+ if len(keep) == 0 or np.any(np.diff(keep) <= 0):
24
+ raise RuntimeError(f"invalid retained indices for {key}")
25
+ cache[key] = keep
26
+ return cache, payload
27
+
28
+
29
+ def install_adapter(
30
+ cache_path: str,
31
+ start_global_draw_id: int,
32
+ global_microbatch_size: int,
33
+ ) -> None:
34
+ from starVLA.dataloader.gr00t_lerobot.datasets import LeRobotMixtureDataset
35
+
36
+ cache, cache_payload = load_cache(cache_path)
37
+ if os.environ.get("FRAMESKIP_SMOKE_FROZEN_MANIFEST") == "1":
38
+ from starVLA.dataloader.infoflow_sampler import ValidAnchorManifest
39
+
40
+ original_build_manifest = LeRobotMixtureDataset._build_valid_anchor_manifest
41
+
42
+ def load_frozen_manifest(self):
43
+ path = self.data_cfg.get("valid_anchor_manifest_path")
44
+ if not path:
45
+ return original_build_manifest(self)
46
+ manifest = ValidAnchorManifest.load(path)
47
+ print(
48
+ f"FrameSkip smoke frozen manifest: {path}",
49
+ flush=True,
50
+ )
51
+ return manifest
52
+
53
+ LeRobotMixtureDataset._build_valid_anchor_manifest = load_frozen_manifest
54
+
55
+ original_getitem = LeRobotMixtureDataset.__getitem__
56
+ period = 6
57
+
58
+ def frameskip_getitem(self, index):
59
+ trace = self.sample_trace(index)
60
+ offset = trace.global_draw_id - start_global_draw_id
61
+ if offset < 0:
62
+ raise RuntimeError("FrameSkip draw precedes the certified continuation start")
63
+ microbatch = offset // global_microbatch_size
64
+ is_pruned = microbatch % period < 5
65
+ logical_index = int(trace.base_index)
66
+ actual_index = logical_index
67
+ if is_pruned:
68
+ key = (trace.dataset_name, str(trace.trajectory_id))
69
+ if key not in cache:
70
+ raise RuntimeError(f"FrameSkip cache is missing required trajectory {key}")
71
+ retained = cache[key]
72
+ position = int(np.searchsorted(retained, logical_index))
73
+ position = int(np.clip(position, 0, len(retained) - 1))
74
+ actual_index = int(retained[position])
75
+
76
+ dataset = self.datasets[trace.dataset_index]
77
+ original_get_step_data = dataset.get_step_data
78
+
79
+ def redirected_get_step_data(trajectory_id, base_index):
80
+ if str(trajectory_id) != str(trace.trajectory_id) or int(base_index) != logical_index:
81
+ raise RuntimeError("unexpected nested dataset read during FrameSkip remapping")
82
+ return original_get_step_data(trajectory_id, actual_index)
83
+
84
+ dataset.get_step_data = redirected_get_step_data
85
+ try:
86
+ sample = original_getitem(self, index)
87
+ finally:
88
+ dataset.get_step_data = original_get_step_data
89
+ sample["sampling_trace"]["frameskip"] = {
90
+ "method": cache_payload["method"],
91
+ "cache_record_count": len(cache_payload["records"]),
92
+ "is_pruned_minibatch": is_pruned,
93
+ "logical_base_index": logical_index,
94
+ "physical_base_index": actual_index,
95
+ "retention_ratio": 0.2 if is_pruned else 1.0,
96
+ }
97
+ return sample
98
+
99
+ LeRobotMixtureDataset.__getitem__ = frameskip_getitem
100
+
101
+
102
+ def main() -> None:
103
+ parser = argparse.ArgumentParser()
104
+ parser.add_argument("--config_yaml", required=True)
105
+ args, clipargs = parser.parse_known_args()
106
+ cache_path = os.environ.get("FRAMESKIP_CACHE")
107
+ if not cache_path:
108
+ raise RuntimeError("FRAMESKIP_CACHE is required")
109
+ start_draw = int(os.environ.get("FRAMESKIP_START_GLOBAL_DRAW_ID", "0"))
110
+ global_microbatch_size = int(
111
+ os.environ.get("FRAMESKIP_GLOBAL_MICROBATCH_SIZE", "2")
112
+ )
113
+ if global_microbatch_size <= 0:
114
+ raise ValueError("FRAMESKIP_GLOBAL_MICROBATCH_SIZE must be positive")
115
+ install_adapter(cache_path, start_draw, global_microbatch_size)
116
+
117
+ from starVLA.model.framework.share_tools import apply_config_compat
118
+ from starVLA.training import train_starvla
119
+ from starVLA.training.trainer_utils.trainer_tools import normalize_dotlist_args
120
+
121
+ cfg = OmegaConf.load(args.config_yaml)
122
+ cfg = OmegaConf.merge(cfg, OmegaConf.from_dotlist(normalize_dotlist_args(clipargs)))
123
+ cfg = apply_config_compat(cfg, strict=True)
124
+ cfg.config_yaml = args.config_yaml
125
+ train_starvla.main(cfg)
126
+
127
+
128
+ if __name__ == "__main__":
129
+ main()
external_baselines/qwen_robotwin_adapted/register_qwen_robotwin_smoke.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Register the existing one-task RoboTwin smoke mixture."""
2
+
3
+ from starVLA.dataloader.gr00t_lerobot.registry import (
4
+ DATASET_NAMED_MIXTURES,
5
+ ROBOT_TYPE_CONFIG_MAP,
6
+ )
7
+
8
+
9
+ MIXTURE_NAME = "qwen_robotwin_smoke_adjust_bottle"
10
+
11
+
12
+ def register_qwen_robotwin_smoke() -> None:
13
+ if "robotwin50" not in ROBOT_TYPE_CONFIG_MAP:
14
+ raise RuntimeError("The RoboTwin robot configuration is unavailable")
15
+ DATASET_NAMED_MIXTURES[MIXTURE_NAME] = [
16
+ ("adjust_bottle", 1.0, "robotwin50"),
17
+ ]
external_baselines/qwen_robotwin_adapted/train_qwen_robotwin_frameskip.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Run FrameSkip for Qwen-RoboTwin using the shared RoboTwin adapter."""
3
+
4
+ import os
5
+
6
+ from external_baselines.qwen_robotwin_adapted.register_qwen_robotwin_smoke import (
7
+ register_qwen_robotwin_smoke,
8
+ )
9
+
10
+
11
+ def main() -> None:
12
+ if os.environ.get("QWEN_ROBOTWIN_REGISTER_SMOKE") == "1":
13
+ register_qwen_robotwin_smoke()
14
+ from external_baselines.frameskip_robotwin_adapted.train_frameskip_robotwin_continuation import (
15
+ main as run,
16
+ )
17
+
18
+ run()
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
external_baselines/qwen_robotwin_adapted/train_qwen_robotwin_keyframe.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Run Keyframe-Focused for Qwen-RoboTwin."""
3
+
4
+ import os
5
+
6
+ from external_baselines.qwen_robotwin_adapted.register_qwen_robotwin_smoke import (
7
+ register_qwen_robotwin_smoke,
8
+ )
9
+
10
+
11
+ def main() -> None:
12
+ if os.environ.get("QWEN_ROBOTWIN_REGISTER_SMOKE") == "1":
13
+ register_qwen_robotwin_smoke()
14
+ from external_baselines.keyframe_focused_adapted.train_keyframe_continuation import (
15
+ main as run,
16
+ )
17
+
18
+ run()
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
external_baselines/qwen_robotwin_adapted/train_qwen_robotwin_loss_is.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Run the canonical always-on Loss-IS implementation for Qwen-RoboTwin."""
3
+
4
+ import os
5
+
6
+ from external_baselines.qwen_robotwin_adapted.register_qwen_robotwin_smoke import (
7
+ register_qwen_robotwin_smoke,
8
+ )
9
+
10
+
11
+ def main() -> None:
12
+ if os.environ.get("QWEN_ROBOTWIN_REGISTER_SMOKE") == "1":
13
+ register_qwen_robotwin_smoke()
14
+ from external_baselines.loss_is_active.train_active_loss_is_continuation import (
15
+ main as run,
16
+ )
17
+
18
+ run()
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
external_baselines/settings/florence_bridge/frameskip.yaml ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ datasets:
2
+ vla_data:
3
+ action_mode: abs
4
+ balance_dataset_weights: false
5
+ balance_trajectory_weights: false
6
+ data_mix: bridge_rt_infoflow
7
+ data_root_dir: /mnt/miaochengyu/starvla-infoflow-runtime/playground/Datasets/OXE_LEROBOT_DATASET
8
+ dataset_content_fingerprint_path: /mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/content_fingerprints.json
9
+ dataset_provenance_path: /home/miaochengyu/starVLA-infoflow-codex/protocols/bridgert/data_provenance.json
10
+ dataset_py: lerobot_datasets
11
+ expected_world_size: 2
12
+ num_workers: 4
13
+ per_device_batch_size: 4
14
+ precomputed_statistics_key_map:
15
+ bridge_orig_lerobot: oxe_bridge
16
+ fractal20220817_data_lerobot: oxe_rt1
17
+ precomputed_statistics_path: /mnt/miaochengyu/starvla-infoflow-runtime/playground/Pretrained_models/Florence-GR00T-Bridge-RT-1/dataset_statistics.json
18
+ temporal_proposal_path: null
19
+ traceable_sampling: true
20
+ valid_anchor_manifest_migration_path: /home/miaochengyu/starVLA-infoflow-codex/external_baselines/frameskip_adapted/preflight/valid_anchor_manifest_migration_20260825.json
21
+ valid_anchor_manifest_path: /mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/valid_anchor_manifest_florence_scratch_v1.json
22
+ video_backend: torchvision_av
23
+ framework:
24
+ action_model:
25
+ action_dim: 7
26
+ action_horizon: 16
27
+ action_model_type: DiT-B
28
+ add_pos_embed: true
29
+ diffusion_model_cfg:
30
+ cross_attention_dim: 1024
31
+ dropout: 0.2
32
+ final_dropout: true
33
+ interleave_self_attention: true
34
+ norm_type: ada_norm
35
+ num_layers: 16
36
+ output_dim: 1024
37
+ positional_embeddings: null
38
+ hidden_size: 1024
39
+ max_seq_len: 1024
40
+ noise_beta_alpha: 1.5
41
+ noise_beta_beta: 1.0
42
+ num_inference_timesteps: 4
43
+ num_target_vision_tokens: 32
44
+ num_timestep_buckets: 1000
45
+ state_dim: 7
46
+ name: QwenGR00T
47
+ qwenvl:
48
+ base_vlm: /mnt/miaochengyu/starvla-infoflow-runtime/playground/Pretrained_models/Florence-2-large
49
+ output_dir: /mnt/miaochengyu/starvla-infoflow-runtime/runs/external-baselines/frameskip-adapted-gaussian-tpi-r02-20260825/frameskip-adapted-gaussian-tpi-r02-seed-117465
50
+ run_id: frameskip-adapted-gaussian-tpi-r02-seed-117465
51
+ run_root_dir: /mnt/miaochengyu/starvla-infoflow-runtime/runs/external-baselines/frameskip-adapted-gaussian-tpi-r02-20260825
52
+ seed: 117465
53
+ trainer:
54
+ freeze_modules: ''
55
+ gradient_accumulation_steps: 8
56
+ learning_rate:
57
+ action_model: 0.0001
58
+ base: 3.0e-05
59
+ qwen_vl_interface: 1.0e-05
60
+ lr_scheduler_type: cosine_with_min_lr
61
+ max_train_steps: 100000
62
+ num_warmup_steps: 5000
63
+ optimizer:
64
+ betas:
65
+ - 0.9
66
+ - 0.95
67
+ eps: 1.0e-08
68
+ name: AdamW
69
+ weight_decay: 1.0e-08
70
+ scheduler_specific_kwargs:
71
+ min_lr: 5.0e-07
external_baselines/settings/florence_bridge/keyframe.yaml ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ datasets:
2
+ vla_data:
3
+ action_mode: abs
4
+ balance_dataset_weights: false
5
+ balance_trajectory_weights: false
6
+ data_mix: bridge_rt_infoflow
7
+ data_root_dir: /home/liuyu/student-workspace/data/runtime/playground/Datasets/OXE_LEROBOT_DATASET
8
+ dataset_content_fingerprint_path: /home/liuyu/student-workspace/data/runtime/manifests/bridgert/content_fingerprints.json
9
+ dataset_provenance_path: /home/liuyu/student-workspace/code/starVLA-infoflow-codex/protocols/bridgert/data_provenance.json
10
+ dataset_py: lerobot_datasets
11
+ expected_world_size: 2
12
+ num_workers: 4
13
+ per_device_batch_size: 4
14
+ precomputed_statistics_key_map:
15
+ bridge_orig_lerobot: oxe_bridge
16
+ fractal20220817_data_lerobot: oxe_rt1
17
+ precomputed_statistics_path: /home/liuyu/student-workspace/data/runtime/playground/Pretrained_models/Florence-GR00T-Bridge-RT-1/dataset_statistics.json
18
+ temporal_proposal_path: null
19
+ traceable_sampling: true
20
+ valid_anchor_manifest_path: /home/liuyu/student-workspace/data/runtime/manifests/bridgert/valid_anchor_manifest_florence_scratch_v1.json
21
+ video_backend: torchvision_av
22
+ framework:
23
+ action_model:
24
+ action_dim: 7
25
+ action_horizon: 16
26
+ action_model_type: DiT-B
27
+ add_pos_embed: true
28
+ diffusion_model_cfg:
29
+ cross_attention_dim: 1024
30
+ dropout: 0.2
31
+ final_dropout: true
32
+ interleave_self_attention: true
33
+ norm_type: ada_norm
34
+ num_layers: 16
35
+ output_dim: 1024
36
+ positional_embeddings: null
37
+ hidden_size: 1024
38
+ max_seq_len: 1024
39
+ noise_beta_alpha: 1.5
40
+ noise_beta_beta: 1.0
41
+ num_inference_timesteps: 4
42
+ num_target_vision_tokens: 32
43
+ num_timestep_buckets: 1000
44
+ state_dim: 7
45
+ name: QwenGR00T
46
+ qwenvl:
47
+ base_vlm: /home/liuyu/student-workspace/data/runtime/playground/Pretrained_models/Florence-2-large
48
+ output_dir: /home/liuyu/student-workspace/outputs/keyframe_focused_adapted/runs/keyframe-focused-adapted-seed-117465-20k-to-20500
49
+ run_id: keyframe-focused-adapted-seed-117465-20k-to-20500
50
+ run_root_dir: /home/liuyu/student-workspace/outputs/keyframe_focused_adapted/runs
51
+ seed: 117465
52
+ trainer:
53
+ freeze_modules: ''
54
+ gradient_accumulation_steps: 8
55
+ learning_rate:
56
+ action_model: 0.0001
57
+ base: 3.0e-05
58
+ qwen_vl_interface: 1.0e-05
59
+ lr_scheduler_type: cosine_with_min_lr
60
+ max_train_steps: 100000
61
+ num_warmup_steps: 5000
62
+ optimizer:
63
+ betas:
64
+ - 0.9
65
+ - 0.95
66
+ eps: 1.0e-08
67
+ name: AdamW
68
+ weight_decay: 1.0e-08
69
+ scheduler_specific_kwargs:
70
+ min_lr: 5.0e-07
external_baselines/settings/florence_bridge/loss_is_active.yaml ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: florence-bridge-active-loss-is-20k-to20500-seed117465
2
+ run_root_dir: /home/liuyu/student-workspace/outputs/active_loss_is_20260907/formal_runs
3
+ seed: 117465
4
+ wandb_entity: null
5
+ wandb_project: Loss-IS-adapted-BridgeRT
6
+ is_debug: false
7
+ version_id: '0.21'
8
+ framework:
9
+ name: QwenGR00T
10
+ qwenvl:
11
+ base_vlm: /home/liuyu/student-workspace/data/runtime/playground/Pretrained_models/Florence-2-large
12
+ attn_implementation: flash_attention_2
13
+ vl_hidden_dim: 2048
14
+ action_model:
15
+ action_model_type: DiT-B
16
+ action_hidden_dim: 1024
17
+ hidden_size: 1024
18
+ add_pos_embed: true
19
+ max_seq_len: 1024
20
+ action_dim: 7
21
+ state_dim: 7
22
+ action_horizon: 16
23
+ repeated_diffusion_steps: 8
24
+ noise_beta_alpha: 1.5
25
+ noise_beta_beta: 1.0
26
+ noise_s: 0.999
27
+ num_timestep_buckets: 1000
28
+ num_inference_timesteps: 4
29
+ num_target_vision_tokens: 32
30
+ diffusion_model_cfg:
31
+ cross_attention_dim: 1024
32
+ dropout: 0.2
33
+ final_dropout: true
34
+ interleave_self_attention: true
35
+ norm_type: ada_norm
36
+ num_layers: 16
37
+ output_dim: 1024
38
+ positional_embeddings: null
39
+ future_action_window_size: 15
40
+ past_action_window_size: 0
41
+ dino:
42
+ dino_backbone: dinov2_vits14
43
+ datasets:
44
+ vla_data:
45
+ dataset_py: lerobot_datasets
46
+ data_root_dir: /home/liuyu/student-workspace/data/runtime/playground/Datasets/OXE_LEROBOT_DATASET
47
+ data_mix: bridge_rt_infoflow
48
+ action_type: delta_ee
49
+ action_mode: abs
50
+ precomputed_statistics_path: /home/liuyu/student-workspace/data/runtime/playground/Pretrained_models/Florence-GR00T-Bridge-RT-1/dataset_statistics.json
51
+ precomputed_statistics_key_map:
52
+ bridge_orig_lerobot: oxe_bridge
53
+ fractal20220817_data_lerobot: oxe_rt1
54
+ CoT_prompt: Your task is {instruction}. To identify the key objects for your task.
55
+ Locate their bounding boxes in [x1,y1,x2,y2] format.
56
+ per_device_batch_size: 4
57
+ load_all_data_for_training: true
58
+ obs_image_size:
59
+ - 224
60
+ - 224
61
+ video_backend: torchvision_av
62
+ num_workers: 4
63
+ balance_dataset_weights: false
64
+ balance_trajectory_weights: false
65
+ traceable_sampling: true
66
+ expected_world_size: 2
67
+ dataset_provenance_path: /home/liuyu/student-workspace/code/starVLA-infoflow-codex/protocols/bridgert/data_provenance.json
68
+ dataset_content_fingerprint_path: /home/liuyu/student-workspace/data/runtime/manifests/bridgert/content_fingerprints.json
69
+ valid_anchor_manifest_path: /home/liuyu/student-workspace/data/runtime/manifests/bridgert/valid_anchor_manifest_florence_scratch_v1.json
70
+ valid_anchor_manifest_migration_path: null
71
+ temporal_proposal_path: null
72
+ transform_rng_seed: 298226
73
+ trainer:
74
+ use_deepspeed: true
75
+ max_train_steps: 100000
76
+ num_warmup_steps: 5000
77
+ save_interval: null
78
+ save_steps:
79
+ - 20500
80
+ evaluation_snapshot_interval: 1000
81
+ eval_interval: 100001
82
+ repeated_diffusion_steps: 4
83
+ pretrained_checkpoint: null
84
+ is_resume: true
85
+ allow_model_only_resume: false
86
+ learning_rate:
87
+ base: 3.0e-05
88
+ qwen_vl_interface: 1.0e-05
89
+ action_model: 0.0001
90
+ lr_scheduler_type: cosine_with_min_lr
91
+ scheduler_specific_kwargs:
92
+ min_lr: 5.0e-07
93
+ freeze_modules: ''
94
+ trainable_modules: ''
95
+ frozen_modules_eval: false
96
+ logging_frequency: 50
97
+ gradient_clipping: 1.0
98
+ gradient_accumulation_steps: 8
99
+ save_format: pt
100
+ save_final_checkpoint: false
101
+ optimizer:
102
+ name: AdamW
103
+ betas:
104
+ - 0.9
105
+ - 0.95
106
+ eps: 1.0e-08
107
+ weight_decay: 1.0e-08
108
+ resume_accelerate_checkpoint: /home/liuyu/student-workspace/data/runtime/runs/florence-scratch-official-100k/florence-scratch-uniform-seed-42-official100k-mb4-eval1k-20260726/checkpoints/steps_20000_accelerate
109
+ branch_parent_seed: 42
110
+ branch_rng_seed: 117465
111
+ model_rng_seed: 588643
112
+ model_rng_trace_key: loss_is_occurrence_id
113
+ sample_loss_weight_key: loss_is_importance_weight
114
+ stop_after_steps: 20500
115
+ loss_is:
116
+ candidate_factor: 3
117
+ seed: 117465
external_baselines/settings/florence_robotwin/frameskip.yaml ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: florence-robotwin-frameskip-10000-to-10500-seed484370
2
+ run_root_dir: /home/liuyu/student-workspace/outputs/robotwin_external_20260831/formal_runs
3
+ seed: 484370
4
+ wandb_entity: null
5
+ wandb_project: InfoFlow-StarVLA-RoboTwin
6
+ is_debug: false
7
+ version_id: '0.21'
8
+ framework:
9
+ name: QwenGR00T
10
+ qwenvl:
11
+ base_vlm: /home/liuyu/student-workspace/data/runtime/playground/Pretrained_models/Florence-2-large
12
+ attn_implementation: eager
13
+ vl_hidden_dim: 1024
14
+ selected_view_indices:
15
+ - 0
16
+ - 1
17
+ - 2
18
+ compile_vision_tower: true
19
+ action_model:
20
+ action_model_type: DiT-B
21
+ action_hidden_dim: 1024
22
+ hidden_size: 1024
23
+ add_pos_embed: true
24
+ max_seq_len: 1024
25
+ action_dim: 14
26
+ state_dim: 0
27
+ action_horizon: 50
28
+ repeated_diffusion_steps: 8
29
+ noise_beta_alpha: 1.5
30
+ noise_beta_beta: 1.0
31
+ noise_s: 0.999
32
+ num_timestep_buckets: 1000
33
+ num_inference_timesteps: 4
34
+ num_target_vision_tokens: 32
35
+ diffusion_model_cfg:
36
+ cross_attention_dim: 1024
37
+ dropout: 0.2
38
+ final_dropout: true
39
+ interleave_self_attention: true
40
+ norm_type: ada_norm
41
+ num_layers: 16
42
+ output_dim: 1024
43
+ positional_embeddings: null
44
+ future_action_window_size: 49
45
+ past_action_window_size: 0
46
+ dino:
47
+ dino_backbone: dinov2_vits14
48
+ datasets:
49
+ vla_data:
50
+ dataset_py: lerobot_datasets
51
+ data_root_dir: /home/liuyu/student-workspace/data/robotwin/RoboTwin
52
+ data_mix: robotwin_all_50
53
+ action_type: abs_qpos
54
+ action_mode: abs
55
+ include_state: false
56
+ sequential_step_sampling: false
57
+ per_device_batch_size: 2
58
+ load_all_data_for_training: true
59
+ image_size:
60
+ - 224
61
+ - 224
62
+ video_backend: pyav
63
+ num_workers: 4
64
+ expected_world_size: 2
65
+ traceable_sampling: true
66
+ dataset_provenance_path: /home/liuyu/student-workspace/data/robotwin/manifests/data_provenance.json
67
+ dataset_content_fingerprint_path: /home/liuyu/student-workspace/data/robotwin/manifests/content_fingerprints.json
68
+ valid_anchor_manifest_path: /home/liuyu/student-workspace/data/robotwin/manifests/valid_anchor_manifest.json
69
+ temporal_proposal_path: null
70
+ transform_rng_seed: 289726
71
+ trainer:
72
+ use_deepspeed: true
73
+ max_train_steps: 150000
74
+ num_warmup_steps: 5000
75
+ save_interval: null
76
+ save_steps:
77
+ - 10500
78
+ eval_interval: null
79
+ repeated_diffusion_steps: 4
80
+ learning_rate:
81
+ base: 1.0e-05
82
+ qwen_vl_interface: 1.0e-05
83
+ action_model: 0.0001
84
+ lr_scheduler_type: cosine_with_min_lr
85
+ scheduler_specific_kwargs:
86
+ min_lr: 5.0e-07
87
+ freeze_modules: ''
88
+ trainable_modules: ''
89
+ frozen_modules_eval: false
90
+ logging_frequency: 50
91
+ gradient_clipping: 1.0
92
+ gradient_accumulation_steps: 16
93
+ save_format: pt
94
+ save_final_checkpoint: false
95
+ optimizer:
96
+ name: AdamW
97
+ betas:
98
+ - 0.9
99
+ - 0.95
100
+ eps: 1.0e-08
101
+ weight_decay: 1.0e-08
102
+ stop_after_steps: 10500
103
+ is_resume: true
104
+ allow_model_only_resume: false
105
+ resume_accelerate_checkpoint: /home/liuyu/student-workspace/data/robotwin/common_start/florence-groot-robotwin-xvla3view-official150k-seed42-20260804/checkpoints/steps_10000_accelerate
106
+ branch_parent_seed: 42
107
+ branch_rng_seed: 484370
108
+ model_rng_seed: 721927
109
+ evaluation_snapshot_interval: null
external_baselines/settings/florence_robotwin/keyframe.yaml ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: florence-robotwin-keyframe-10000-to-10500-seed484370
2
+ run_root_dir: /home/liuyu/student-workspace/outputs/robotwin_external_20260831/formal_runs
3
+ seed: 484370
4
+ wandb_entity: null
5
+ wandb_project: InfoFlow-StarVLA-RoboTwin
6
+ is_debug: false
7
+ version_id: '0.21'
8
+ framework:
9
+ name: QwenGR00T
10
+ qwenvl:
11
+ base_vlm: /home/liuyu/student-workspace/data/runtime/playground/Pretrained_models/Florence-2-large
12
+ attn_implementation: eager
13
+ vl_hidden_dim: 1024
14
+ selected_view_indices:
15
+ - 0
16
+ - 1
17
+ - 2
18
+ compile_vision_tower: true
19
+ action_model:
20
+ action_model_type: DiT-B
21
+ action_hidden_dim: 1024
22
+ hidden_size: 1024
23
+ add_pos_embed: true
24
+ max_seq_len: 1024
25
+ action_dim: 14
26
+ state_dim: 0
27
+ action_horizon: 50
28
+ repeated_diffusion_steps: 8
29
+ noise_beta_alpha: 1.5
30
+ noise_beta_beta: 1.0
31
+ noise_s: 0.999
32
+ num_timestep_buckets: 1000
33
+ num_inference_timesteps: 4
34
+ num_target_vision_tokens: 32
35
+ diffusion_model_cfg:
36
+ cross_attention_dim: 1024
37
+ dropout: 0.2
38
+ final_dropout: true
39
+ interleave_self_attention: true
40
+ norm_type: ada_norm
41
+ num_layers: 16
42
+ output_dim: 1024
43
+ positional_embeddings: null
44
+ future_action_window_size: 49
45
+ past_action_window_size: 0
46
+ dino:
47
+ dino_backbone: dinov2_vits14
48
+ datasets:
49
+ vla_data:
50
+ dataset_py: lerobot_datasets
51
+ data_root_dir: /home/liuyu/student-workspace/data/robotwin/RoboTwin
52
+ data_mix: robotwin_all_50
53
+ action_type: abs_qpos
54
+ action_mode: abs
55
+ include_state: false
56
+ sequential_step_sampling: false
57
+ per_device_batch_size: 2
58
+ load_all_data_for_training: true
59
+ image_size:
60
+ - 224
61
+ - 224
62
+ video_backend: pyav
63
+ num_workers: 4
64
+ expected_world_size: 2
65
+ traceable_sampling: true
66
+ dataset_provenance_path: /home/liuyu/student-workspace/data/robotwin/manifests/data_provenance.json
67
+ dataset_content_fingerprint_path: /home/liuyu/student-workspace/data/robotwin/manifests/content_fingerprints.json
68
+ valid_anchor_manifest_path: /home/liuyu/student-workspace/data/robotwin/manifests/valid_anchor_manifest.json
69
+ temporal_proposal_path: null
70
+ transform_rng_seed: 289726
71
+ trainer:
72
+ use_deepspeed: true
73
+ max_train_steps: 150000
74
+ num_warmup_steps: 5000
75
+ save_interval: null
76
+ save_steps:
77
+ - 10500
78
+ eval_interval: null
79
+ repeated_diffusion_steps: 4
80
+ learning_rate:
81
+ base: 1.0e-05
82
+ qwen_vl_interface: 1.0e-05
83
+ action_model: 0.0001
84
+ lr_scheduler_type: cosine_with_min_lr
85
+ scheduler_specific_kwargs:
86
+ min_lr: 5.0e-07
87
+ freeze_modules: ''
88
+ trainable_modules: ''
89
+ frozen_modules_eval: false
90
+ logging_frequency: 50
91
+ gradient_clipping: 1.0
92
+ gradient_accumulation_steps: 16
93
+ save_format: pt
94
+ save_final_checkpoint: false
95
+ optimizer:
96
+ name: AdamW
97
+ betas:
98
+ - 0.9
99
+ - 0.95
100
+ eps: 1.0e-08
101
+ weight_decay: 1.0e-08
102
+ stop_after_steps: 10500
103
+ is_resume: true
104
+ allow_model_only_resume: false
105
+ resume_accelerate_checkpoint: /home/liuyu/student-workspace/data/robotwin/common_start/florence-groot-robotwin-xvla3view-official150k-seed42-20260804/checkpoints/steps_10000_accelerate
106
+ branch_parent_seed: 42
107
+ branch_rng_seed: 484370
108
+ model_rng_seed: 721927
109
+ evaluation_snapshot_interval: null
110
+ sample_loss_weight_key: keyframe_weight
external_baselines/settings/florence_robotwin/loss_is_active.yaml ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: florence-robotwin-active-loss-is-10000-to10500-seed484370
2
+ run_root_dir: /home/liuyu/student-workspace/outputs/active_loss_is_20260907/formal_runs
3
+ seed: 484370
4
+ wandb_entity: null
5
+ wandb_project: InfoFlow-StarVLA-RoboTwin
6
+ is_debug: false
7
+ version_id: '0.21'
8
+ framework:
9
+ name: QwenGR00T
10
+ qwenvl:
11
+ base_vlm: /home/liuyu/student-workspace/data/runtime/playground/Pretrained_models/Florence-2-large
12
+ attn_implementation: eager
13
+ vl_hidden_dim: 1024
14
+ selected_view_indices:
15
+ - 0
16
+ - 1
17
+ - 2
18
+ compile_vision_tower: true
19
+ action_model:
20
+ action_model_type: DiT-B
21
+ action_hidden_dim: 1024
22
+ hidden_size: 1024
23
+ add_pos_embed: true
24
+ max_seq_len: 1024
25
+ action_dim: 14
26
+ state_dim: 0
27
+ action_horizon: 50
28
+ repeated_diffusion_steps: 8
29
+ noise_beta_alpha: 1.5
30
+ noise_beta_beta: 1.0
31
+ noise_s: 0.999
32
+ num_timestep_buckets: 1000
33
+ num_inference_timesteps: 4
34
+ num_target_vision_tokens: 32
35
+ diffusion_model_cfg:
36
+ cross_attention_dim: 1024
37
+ dropout: 0.2
38
+ final_dropout: true
39
+ interleave_self_attention: true
40
+ norm_type: ada_norm
41
+ num_layers: 16
42
+ output_dim: 1024
43
+ positional_embeddings: null
44
+ future_action_window_size: 49
45
+ past_action_window_size: 0
46
+ dino:
47
+ dino_backbone: dinov2_vits14
48
+ datasets:
49
+ vla_data:
50
+ dataset_py: lerobot_datasets
51
+ data_root_dir: /home/liuyu/student-workspace/data/robotwin/RoboTwin
52
+ data_mix: robotwin_all_50
53
+ action_type: abs_qpos
54
+ action_mode: abs
55
+ include_state: false
56
+ sequential_step_sampling: false
57
+ per_device_batch_size: 2
58
+ load_all_data_for_training: true
59
+ image_size:
60
+ - 224
61
+ - 224
62
+ video_backend: pyav
63
+ num_workers: 4
64
+ expected_world_size: 2
65
+ traceable_sampling: true
66
+ dataset_provenance_path: /home/liuyu/student-workspace/data/robotwin/manifests/data_provenance.json
67
+ dataset_content_fingerprint_path: /home/liuyu/student-workspace/data/robotwin/manifests/content_fingerprints.json
68
+ valid_anchor_manifest_path: /home/liuyu/student-workspace/data/robotwin/manifests/valid_anchor_manifest.json
69
+ temporal_proposal_path: null
70
+ transform_rng_seed: 289726
71
+ trainer:
72
+ use_deepspeed: true
73
+ max_train_steps: 150000
74
+ num_warmup_steps: 5000
75
+ save_interval: null
76
+ save_steps:
77
+ - 10500
78
+ eval_interval: null
79
+ repeated_diffusion_steps: 4
80
+ learning_rate:
81
+ base: 1.0e-05
82
+ qwen_vl_interface: 1.0e-05
83
+ action_model: 0.0001
84
+ lr_scheduler_type: cosine_with_min_lr
85
+ scheduler_specific_kwargs:
86
+ min_lr: 5.0e-07
87
+ freeze_modules: ''
88
+ trainable_modules: ''
89
+ frozen_modules_eval: false
90
+ logging_frequency: 50
91
+ gradient_clipping: 1.0
92
+ gradient_accumulation_steps: 16
93
+ save_format: pt
94
+ save_final_checkpoint: false
95
+ optimizer:
96
+ name: AdamW
97
+ betas:
98
+ - 0.9
99
+ - 0.95
100
+ eps: 1.0e-08
101
+ weight_decay: 1.0e-08
102
+ stop_after_steps: 10500
103
+ is_resume: true
104
+ allow_model_only_resume: false
105
+ resume_accelerate_checkpoint: /home/liuyu/student-workspace/data/robotwin/common_start/florence-groot-robotwin-xvla3view-official150k-seed42-20260804/checkpoints/steps_10000_accelerate
106
+ branch_parent_seed: 42
107
+ branch_rng_seed: 484370
108
+ model_rng_seed: 721927
109
+ evaluation_snapshot_interval: null
110
+ sample_loss_weight_key: loss_is_importance_weight
111
+ model_rng_trace_key: loss_is_occurrence_id
112
+ loss_is:
113
+ candidate_factor: 3
114
+ seed: 484370
external_baselines/settings/qwen_bridge/frameskip.yaml ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ datasets:
2
+ vla_data:
3
+ CoT_prompt: Your task is {instruction}. To identify the key objects for your task.
4
+ Locate their bounding boxes in [x1,y1,x2,y2] format.
5
+ action_mode: abs
6
+ balance_dataset_weights: false
7
+ balance_trajectory_weights: false
8
+ data_mix: bridge_rt_infoflow
9
+ data_root_dir: /data/yanfei/qwen_bridge_assets/OXE_LEROBOT_DATASET
10
+ dataset_content_fingerprint_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/content_fingerprints.json
11
+ dataset_provenance_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/home/miaochengyu/starVLA-infoflow-codex/protocols/bridgert/data_provenance.json
12
+ dataset_py: lerobot_datasets
13
+ expected_world_size: 1
14
+ num_workers: 1
15
+ per_device_batch_size: 2
16
+ precomputed_statistics_key_map:
17
+ bridge_orig_lerobot: oxe_bridge
18
+ fractal20220817_data_lerobot: oxe_rt1
19
+ precomputed_statistics_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/playground/Pretrained_models/Florence-GR00T-Bridge-RT-1/dataset_statistics.json
20
+ temporal_proposal_path: null
21
+ traceable_sampling: true
22
+ valid_anchor_manifest_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/valid_anchor_manifest_florence_scratch_v1.json
23
+ video_backend: torchvision_av
24
+ framework:
25
+ action_model:
26
+ action_dim: 7
27
+ action_horizon: 16
28
+ action_model_type: DiT-B
29
+ add_pos_embed: true
30
+ diffusion_model_cfg:
31
+ cross_attention_dim: 2560
32
+ dropout: 0.2
33
+ final_dropout: true
34
+ interleave_self_attention: true
35
+ norm_type: ada_norm
36
+ num_layers: 16
37
+ output_dim: 1024
38
+ positional_embeddings: null
39
+ hidden_size: 1024
40
+ max_seq_len: 1024
41
+ noise_beta_alpha: 1.5
42
+ noise_beta_beta: 1.0
43
+ noise_s: 0.999
44
+ num_inference_timesteps: 4
45
+ num_target_vision_tokens: 32
46
+ num_timestep_buckets: 1000
47
+ state_dim: 7
48
+ name: QwenGR00T
49
+ qwenvl:
50
+ attn_implementation: flash_attention_2
51
+ base_vlm: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/playground/Pretrained_models/Qwen3-VL-4B-Instruct
52
+ output_dir: /data/yanfei/qwen_bridge_external_baselines_29k_20260912/runs/qwen3vl-bridge-frameskip-29k-to29500-seed42
53
+ run_id: qwen3vl-bridge-frameskip-29k-to29500-seed42
54
+ run_root_dir: /data/yanfei/qwen_bridge_external_baselines_29k_20260912/runs
55
+ seed: 42
56
+ trainer:
57
+ allow_model_only_resume: false
58
+ eval_interval: null
59
+ evaluation_snapshot_end_step: null
60
+ evaluation_snapshot_interval: null
61
+ evaluation_snapshot_start_step: null
62
+ freeze_modules: ''
63
+ frozen_modules_eval: false
64
+ gradient_accumulation_steps: 24
65
+ gradient_clipping: 1.0
66
+ is_resume: true
67
+ learning_rate:
68
+ action_model: 0.0001
69
+ base: 3.0e-05
70
+ qwen_vl_interface: 1.0e-05
71
+ logging_frequency: 10
72
+ lr_scheduler_type: cosine_with_min_lr
73
+ max_train_steps: 100000
74
+ num_warmup_steps: 10000
75
+ optimizer:
76
+ betas:
77
+ - 0.9
78
+ - 0.95
79
+ eps: 1.0e-08
80
+ name: AdamW8bit
81
+ weight_decay: 1.0e-08
82
+ pretrained_checkpoint: null
83
+ repeated_diffusion_steps: 4
84
+ save_format: pt
85
+ save_interval: null
86
+ save_steps:
87
+ - 29500
88
+ scheduler_specific_kwargs:
89
+ min_lr: 5.0e-07
90
+ stop_after_steps: 29500
91
+ trainable_modules: ''
92
+ wandb_entity: null
93
+ wandb_project: QwenBridge-CommonStart
external_baselines/settings/qwen_bridge/keyframe.yaml ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ datasets:
2
+ vla_data:
3
+ CoT_prompt: Your task is {instruction}. To identify the key objects for your task.
4
+ Locate their bounding boxes in [x1,y1,x2,y2] format.
5
+ action_mode: abs
6
+ balance_dataset_weights: false
7
+ balance_trajectory_weights: false
8
+ data_mix: bridge_rt_infoflow
9
+ data_root_dir: /data/yanfei/qwen_bridge_assets/OXE_LEROBOT_DATASET
10
+ dataset_content_fingerprint_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/content_fingerprints.json
11
+ dataset_provenance_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/home/miaochengyu/starVLA-infoflow-codex/protocols/bridgert/data_provenance.json
12
+ dataset_py: lerobot_datasets
13
+ expected_world_size: 1
14
+ num_workers: 1
15
+ per_device_batch_size: 2
16
+ precomputed_statistics_key_map:
17
+ bridge_orig_lerobot: oxe_bridge
18
+ fractal20220817_data_lerobot: oxe_rt1
19
+ precomputed_statistics_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/playground/Pretrained_models/Florence-GR00T-Bridge-RT-1/dataset_statistics.json
20
+ temporal_proposal_path: null
21
+ traceable_sampling: true
22
+ valid_anchor_manifest_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/valid_anchor_manifest_florence_scratch_v1.json
23
+ video_backend: torchvision_av
24
+ framework:
25
+ action_model:
26
+ action_dim: 7
27
+ action_horizon: 16
28
+ action_model_type: DiT-B
29
+ add_pos_embed: true
30
+ diffusion_model_cfg:
31
+ cross_attention_dim: 2560
32
+ dropout: 0.2
33
+ final_dropout: true
34
+ interleave_self_attention: true
35
+ norm_type: ada_norm
36
+ num_layers: 16
37
+ output_dim: 1024
38
+ positional_embeddings: null
39
+ hidden_size: 1024
40
+ max_seq_len: 1024
41
+ noise_beta_alpha: 1.5
42
+ noise_beta_beta: 1.0
43
+ noise_s: 0.999
44
+ num_inference_timesteps: 4
45
+ num_target_vision_tokens: 32
46
+ num_timestep_buckets: 1000
47
+ state_dim: 7
48
+ name: QwenGR00T
49
+ qwenvl:
50
+ attn_implementation: flash_attention_2
51
+ base_vlm: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/playground/Pretrained_models/Qwen3-VL-4B-Instruct
52
+ output_dir: /data/yanfei/qwen_bridge_external_baselines_29k_20260912/runs/qwen3vl-bridge-keyframe-29k-to29500-seed42
53
+ run_id: qwen3vl-bridge-keyframe-29k-to29500-seed42
54
+ run_root_dir: /data/yanfei/qwen_bridge_external_baselines_29k_20260912/runs
55
+ seed: 42
56
+ trainer:
57
+ allow_model_only_resume: false
58
+ eval_interval: null
59
+ evaluation_snapshot_end_step: null
60
+ evaluation_snapshot_interval: null
61
+ evaluation_snapshot_start_step: null
62
+ freeze_modules: ''
63
+ frozen_modules_eval: false
64
+ gradient_accumulation_steps: 24
65
+ gradient_clipping: 1.0
66
+ is_resume: true
67
+ learning_rate:
68
+ action_model: 0.0001
69
+ base: 3.0e-05
70
+ qwen_vl_interface: 1.0e-05
71
+ logging_frequency: 10
72
+ lr_scheduler_type: cosine_with_min_lr
73
+ max_train_steps: 100000
74
+ num_warmup_steps: 10000
75
+ optimizer:
76
+ betas:
77
+ - 0.9
78
+ - 0.95
79
+ eps: 1.0e-08
80
+ name: AdamW8bit
81
+ weight_decay: 1.0e-08
82
+ pretrained_checkpoint: null
83
+ repeated_diffusion_steps: 4
84
+ sample_loss_weight_key: keyframe_weight
85
+ save_format: pt
86
+ save_interval: null
87
+ save_steps:
88
+ - 29500
89
+ scheduler_specific_kwargs:
90
+ min_lr: 5.0e-07
91
+ stop_after_steps: 29500
92
+ trainable_modules: ''
93
+ wandb_entity: null
94
+ wandb_project: QwenBridge-CommonStart
external_baselines/settings/qwen_bridge/loss_is_active.yaml ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ datasets:
2
+ vla_data:
3
+ CoT_prompt: Your task is {instruction}. To identify the key objects for your task.
4
+ Locate their bounding boxes in [x1,y1,x2,y2] format.
5
+ action_mode: abs
6
+ balance_dataset_weights: false
7
+ balance_trajectory_weights: false
8
+ data_mix: bridge_rt_infoflow
9
+ data_root_dir: /data/yanfei/qwen_bridge_assets/OXE_LEROBOT_DATASET
10
+ dataset_content_fingerprint_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/content_fingerprints.json
11
+ dataset_provenance_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/home/miaochengyu/starVLA-infoflow-codex/protocols/bridgert/data_provenance.json
12
+ dataset_py: lerobot_datasets
13
+ expected_world_size: 1
14
+ num_workers: 1
15
+ per_device_batch_size: 2
16
+ precomputed_statistics_key_map:
17
+ bridge_orig_lerobot: oxe_bridge
18
+ fractal20220817_data_lerobot: oxe_rt1
19
+ precomputed_statistics_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/playground/Pretrained_models/Florence-GR00T-Bridge-RT-1/dataset_statistics.json
20
+ temporal_proposal_path: null
21
+ traceable_sampling: true
22
+ valid_anchor_manifest_path: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/valid_anchor_manifest_florence_scratch_v1.json
23
+ video_backend: torchvision_av
24
+ framework:
25
+ action_model:
26
+ action_dim: 7
27
+ action_horizon: 16
28
+ action_model_type: DiT-B
29
+ add_pos_embed: true
30
+ diffusion_model_cfg:
31
+ cross_attention_dim: 2560
32
+ dropout: 0.2
33
+ final_dropout: true
34
+ interleave_self_attention: true
35
+ norm_type: ada_norm
36
+ num_layers: 16
37
+ output_dim: 1024
38
+ positional_embeddings: null
39
+ hidden_size: 1024
40
+ max_seq_len: 1024
41
+ noise_beta_alpha: 1.5
42
+ noise_beta_beta: 1.0
43
+ noise_s: 0.999
44
+ num_inference_timesteps: 4
45
+ num_target_vision_tokens: 32
46
+ num_timestep_buckets: 1000
47
+ state_dim: 7
48
+ name: QwenGR00T
49
+ qwenvl:
50
+ attn_implementation: flash_attention_2
51
+ base_vlm: /home/yanfei/miaochengyu/robotwin-infoflow/legacy_bridge_runtime/compat/mnt/miaochengyu/starvla-infoflow-runtime/playground/Pretrained_models/Qwen3-VL-4B-Instruct
52
+ output_dir: /data/yanfei/qwen_bridge_external_baselines_29k_20260912/runs/qwen3vl-bridge-lossis-29k-to29500-seed42
53
+ run_id: qwen3vl-bridge-lossis-29k-to29500-seed42
54
+ run_root_dir: /data/yanfei/qwen_bridge_external_baselines_29k_20260912/runs
55
+ seed: 42
56
+ trainer:
57
+ allow_model_only_resume: false
58
+ eval_interval: null
59
+ evaluation_snapshot_end_step: null
60
+ evaluation_snapshot_interval: null
61
+ evaluation_snapshot_start_step: null
62
+ freeze_modules: ''
63
+ frozen_modules_eval: false
64
+ gradient_accumulation_steps: 24
65
+ gradient_clipping: 1.0
66
+ is_resume: true
67
+ learning_rate:
68
+ action_model: 0.0001
69
+ base: 3.0e-05
70
+ qwen_vl_interface: 1.0e-05
71
+ logging_frequency: 10
72
+ loss_is:
73
+ candidate_factor: 3
74
+ initial_branch_step: 29000
75
+ seed: 42
76
+ lr_scheduler_type: cosine_with_min_lr
77
+ max_train_steps: 100000
78
+ model_rng_trace_key: loss_is_occurrence_id
79
+ num_warmup_steps: 10000
80
+ optimizer:
81
+ betas:
82
+ - 0.9
83
+ - 0.95
84
+ eps: 1.0e-08
85
+ name: AdamW8bit
86
+ weight_decay: 1.0e-08
87
+ pretrained_checkpoint: null
88
+ repeated_diffusion_steps: 4
89
+ sample_loss_weight_key: loss_is_importance_weight
90
+ save_format: pt
91
+ save_interval: null
92
+ save_steps:
93
+ - 29500
94
+ scheduler_specific_kwargs:
95
+ min_lr: 5.0e-07
96
+ stop_after_steps: 29500
97
+ trainable_modules: ''
98
+ wandb_entity: null
99
+ wandb_project: QwenBridge-CommonStart
external_baselines/settings/qwen_robotwin/frameskip.yaml ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: qwen-robotwin-frameskip-11000-to-11500-seed484370
2
+ run_root_dir: /home/liuyu/student-workspace/outputs/robotwin_external_20260831/formal_runs
3
+ seed: 484370
4
+ wandb_entity: null
5
+ wandb_project: InfoFlow-StarVLA-RoboTwin
6
+ is_debug: false
7
+ version_id: '0.21'
8
+ framework:
9
+ name: QwenGR00T
10
+ qwenvl:
11
+ base_vlm: /home/liuyu/student-workspace/data/runtime/playground/Pretrained_models/Qwen3-VL-4B-Instruct
12
+ attn_implementation: flash_attention_2
13
+ vl_hidden_dim: 2048
14
+ action_model:
15
+ action_model_type: DiT-B
16
+ action_hidden_dim: 2048
17
+ hidden_size: 1024
18
+ add_pos_embed: true
19
+ max_seq_len: 1024
20
+ action_dim: 14
21
+ state_dim: 0
22
+ action_horizon: 50
23
+ repeated_diffusion_steps: 8
24
+ noise_beta_alpha: 1.5
25
+ noise_beta_beta: 1.0
26
+ noise_s: 0.999
27
+ num_timestep_buckets: 1000
28
+ num_inference_timesteps: 4
29
+ num_target_vision_tokens: 32
30
+ diffusion_model_cfg:
31
+ cross_attention_dim: 2560
32
+ dropout: 0.2
33
+ final_dropout: true
34
+ interleave_self_attention: true
35
+ norm_type: ada_norm
36
+ num_layers: 16
37
+ output_dim: 1024
38
+ positional_embeddings: null
39
+ future_action_window_size: 49
40
+ past_action_window_size: 0
41
+ dino:
42
+ dino_backbone: dinov2_vits14
43
+ datasets:
44
+ vla_data:
45
+ dataset_py: lerobot_datasets
46
+ data_root_dir: /home/liuyu/student-workspace/data/robotwin/RoboTwin
47
+ data_mix: robotwin_all_50
48
+ action_type: abs_qpos
49
+ action_mode: abs
50
+ include_state: false
51
+ sequential_step_sampling: false
52
+ per_device_batch_size: 5
53
+ load_all_data_for_training: true
54
+ image_size:
55
+ - 224
56
+ - 224
57
+ video_backend: pyav
58
+ num_workers: 4
59
+ expected_world_size: 2
60
+ traceable_sampling: true
61
+ dataset_provenance_path: /home/liuyu/student-workspace/data/robotwin/manifests/data_provenance.json
62
+ dataset_content_fingerprint_path: /home/liuyu/student-workspace/data/robotwin/manifests/content_fingerprints.json
63
+ valid_anchor_manifest_path: /home/liuyu/student-workspace/data/robotwin/manifests/valid_anchor_manifest.json
64
+ transform_rng_seed: 289726
65
+ temporal_proposal_path: null
66
+ trainer:
67
+ use_deepspeed: true
68
+ max_train_steps: 150000
69
+ num_warmup_steps: 5000
70
+ save_interval: null
71
+ save_steps:
72
+ - 11500
73
+ evaluation_snapshot_interval: null
74
+ evaluation_snapshot_start_step: 10000
75
+ evaluation_snapshot_end_step: 20000
76
+ eval_interval: null
77
+ repeated_diffusion_steps: 4
78
+ learning_rate:
79
+ base: 1.0e-05
80
+ qwen_vl_interface: 1.0e-05
81
+ action_model: 0.0001
82
+ lr_scheduler_type: cosine_with_min_lr
83
+ scheduler_specific_kwargs:
84
+ min_lr: 5.0e-07
85
+ freeze_modules: ''
86
+ trainable_modules: ''
87
+ frozen_modules_eval: false
88
+ logging_frequency: 10
89
+ gradient_clipping: 1.0
90
+ gradient_accumulation_steps: 4
91
+ save_format: pt
92
+ save_final_checkpoint: false
93
+ save_consolidated_model: false
94
+ optimizer:
95
+ name: AdamW8bit
96
+ betas:
97
+ - 0.9
98
+ - 0.95
99
+ eps: 1.0e-08
100
+ weight_decay: 1.0e-08
101
+ stop_after_steps: 11500
102
+ is_resume: true
103
+ allow_model_only_resume: false
104
+ resume_accelerate_checkpoint: /home/liuyu/student-workspace/data/robotwin/common_start/qwen3vl-groot-robotwin-official150k-adamw8bit-mb5-ga4-seed42-20260810/checkpoints/steps_11000_accelerate
105
+ branch_parent_seed: 42
106
+ branch_rng_seed: 484370
107
+ model_rng_seed: 721927
external_baselines/settings/qwen_robotwin/keyframe.yaml ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: qwen-robotwin-keyframe-11000-to-11500-seed484370
2
+ run_root_dir: /home/liuyu/student-workspace/outputs/robotwin_external_20260831/formal_runs
3
+ seed: 484370
4
+ wandb_entity: null
5
+ wandb_project: InfoFlow-StarVLA-RoboTwin
6
+ is_debug: false
7
+ version_id: '0.21'
8
+ framework:
9
+ name: QwenGR00T
10
+ qwenvl:
11
+ base_vlm: /home/liuyu/student-workspace/data/runtime/playground/Pretrained_models/Qwen3-VL-4B-Instruct
12
+ attn_implementation: flash_attention_2
13
+ vl_hidden_dim: 2048
14
+ action_model:
15
+ action_model_type: DiT-B
16
+ action_hidden_dim: 2048
17
+ hidden_size: 1024
18
+ add_pos_embed: true
19
+ max_seq_len: 1024
20
+ action_dim: 14
21
+ state_dim: 0
22
+ action_horizon: 50
23
+ repeated_diffusion_steps: 8
24
+ noise_beta_alpha: 1.5
25
+ noise_beta_beta: 1.0
26
+ noise_s: 0.999
27
+ num_timestep_buckets: 1000
28
+ num_inference_timesteps: 4
29
+ num_target_vision_tokens: 32
30
+ diffusion_model_cfg:
31
+ cross_attention_dim: 2560
32
+ dropout: 0.2
33
+ final_dropout: true
34
+ interleave_self_attention: true
35
+ norm_type: ada_norm
36
+ num_layers: 16
37
+ output_dim: 1024
38
+ positional_embeddings: null
39
+ future_action_window_size: 49
40
+ past_action_window_size: 0
41
+ dino:
42
+ dino_backbone: dinov2_vits14
43
+ datasets:
44
+ vla_data:
45
+ dataset_py: lerobot_datasets
46
+ data_root_dir: /home/liuyu/student-workspace/data/robotwin/RoboTwin
47
+ data_mix: robotwin_all_50
48
+ action_type: abs_qpos
49
+ action_mode: abs
50
+ include_state: false
51
+ sequential_step_sampling: false
52
+ per_device_batch_size: 5
53
+ load_all_data_for_training: true
54
+ image_size:
55
+ - 224
56
+ - 224
57
+ video_backend: pyav
58
+ num_workers: 4
59
+ expected_world_size: 2
60
+ traceable_sampling: true
61
+ dataset_provenance_path: /home/liuyu/student-workspace/data/robotwin/manifests/data_provenance.json
62
+ dataset_content_fingerprint_path: /home/liuyu/student-workspace/data/robotwin/manifests/content_fingerprints.json
63
+ valid_anchor_manifest_path: /home/liuyu/student-workspace/data/robotwin/manifests/valid_anchor_manifest.json
64
+ transform_rng_seed: 289726
65
+ temporal_proposal_path: null
66
+ trainer:
67
+ use_deepspeed: true
68
+ max_train_steps: 150000
69
+ num_warmup_steps: 5000
70
+ save_interval: null
71
+ save_steps:
72
+ - 11500
73
+ evaluation_snapshot_interval: null
74
+ evaluation_snapshot_start_step: 10000
75
+ evaluation_snapshot_end_step: 20000
76
+ eval_interval: null
77
+ repeated_diffusion_steps: 4
78
+ learning_rate:
79
+ base: 1.0e-05
80
+ qwen_vl_interface: 1.0e-05
81
+ action_model: 0.0001
82
+ lr_scheduler_type: cosine_with_min_lr
83
+ scheduler_specific_kwargs:
84
+ min_lr: 5.0e-07
85
+ freeze_modules: ''
86
+ trainable_modules: ''
87
+ frozen_modules_eval: false
88
+ logging_frequency: 10
89
+ gradient_clipping: 1.0
90
+ gradient_accumulation_steps: 4
91
+ save_format: pt
92
+ save_final_checkpoint: false
93
+ save_consolidated_model: false
94
+ optimizer:
95
+ name: AdamW8bit
96
+ betas:
97
+ - 0.9
98
+ - 0.95
99
+ eps: 1.0e-08
100
+ weight_decay: 1.0e-08
101
+ stop_after_steps: 11500
102
+ is_resume: true
103
+ allow_model_only_resume: false
104
+ resume_accelerate_checkpoint: /home/liuyu/student-workspace/data/robotwin/common_start/qwen3vl-groot-robotwin-official150k-adamw8bit-mb5-ga4-seed42-20260810/checkpoints/steps_11000_accelerate
105
+ branch_parent_seed: 42
106
+ branch_rng_seed: 484370
107
+ model_rng_seed: 721927
108
+ sample_loss_weight_key: keyframe_weight
external_baselines/settings/qwen_robotwin/loss_is_active.yaml ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: qwen-robotwin-active-loss-is-11000-to11500-seed484370
2
+ run_root_dir: /home/liuyu/student-workspace/outputs/active_loss_is_20260907/formal_runs
3
+ seed: 484370
4
+ wandb_entity: null
5
+ wandb_project: InfoFlow-StarVLA-RoboTwin
6
+ is_debug: false
7
+ version_id: '0.21'
8
+ framework:
9
+ name: QwenGR00T
10
+ qwenvl:
11
+ base_vlm: /home/liuyu/student-workspace/data/runtime/playground/Pretrained_models/Qwen3-VL-4B-Instruct
12
+ attn_implementation: flash_attention_2
13
+ vl_hidden_dim: 2048
14
+ action_model:
15
+ action_model_type: DiT-B
16
+ action_hidden_dim: 2048
17
+ hidden_size: 1024
18
+ add_pos_embed: true
19
+ max_seq_len: 1024
20
+ action_dim: 14
21
+ state_dim: 0
22
+ action_horizon: 50
23
+ repeated_diffusion_steps: 8
24
+ noise_beta_alpha: 1.5
25
+ noise_beta_beta: 1.0
26
+ noise_s: 0.999
27
+ num_timestep_buckets: 1000
28
+ num_inference_timesteps: 4
29
+ num_target_vision_tokens: 32
30
+ diffusion_model_cfg:
31
+ cross_attention_dim: 2560
32
+ dropout: 0.2
33
+ final_dropout: true
34
+ interleave_self_attention: true
35
+ norm_type: ada_norm
36
+ num_layers: 16
37
+ output_dim: 1024
38
+ positional_embeddings: null
39
+ future_action_window_size: 49
40
+ past_action_window_size: 0
41
+ dino:
42
+ dino_backbone: dinov2_vits14
43
+ datasets:
44
+ vla_data:
45
+ dataset_py: lerobot_datasets
46
+ data_root_dir: /home/liuyu/student-workspace/data/robotwin/RoboTwin
47
+ data_mix: robotwin_all_50
48
+ action_type: abs_qpos
49
+ action_mode: abs
50
+ include_state: false
51
+ sequential_step_sampling: false
52
+ per_device_batch_size: 5
53
+ load_all_data_for_training: true
54
+ image_size:
55
+ - 224
56
+ - 224
57
+ video_backend: pyav
58
+ num_workers: 4
59
+ expected_world_size: 2
60
+ traceable_sampling: true
61
+ dataset_provenance_path: /home/liuyu/student-workspace/data/robotwin/manifests/data_provenance.json
62
+ dataset_content_fingerprint_path: /home/liuyu/student-workspace/data/robotwin/manifests/content_fingerprints.json
63
+ valid_anchor_manifest_path: /home/liuyu/student-workspace/data/robotwin/manifests/valid_anchor_manifest.json
64
+ transform_rng_seed: 289726
65
+ temporal_proposal_path: null
66
+ trainer:
67
+ use_deepspeed: true
68
+ max_train_steps: 150000
69
+ num_warmup_steps: 5000
70
+ save_interval: null
71
+ save_steps:
72
+ - 11500
73
+ evaluation_snapshot_interval: null
74
+ evaluation_snapshot_start_step: 10000
75
+ evaluation_snapshot_end_step: 20000
76
+ eval_interval: null
77
+ repeated_diffusion_steps: 4
78
+ learning_rate:
79
+ base: 1.0e-05
80
+ qwen_vl_interface: 1.0e-05
81
+ action_model: 0.0001
82
+ lr_scheduler_type: cosine_with_min_lr
83
+ scheduler_specific_kwargs:
84
+ min_lr: 5.0e-07
85
+ freeze_modules: ''
86
+ trainable_modules: ''
87
+ frozen_modules_eval: false
88
+ logging_frequency: 10
89
+ gradient_clipping: 1.0
90
+ gradient_accumulation_steps: 4
91
+ save_format: pt
92
+ save_final_checkpoint: false
93
+ save_consolidated_model: false
94
+ optimizer:
95
+ name: AdamW8bit
96
+ betas:
97
+ - 0.9
98
+ - 0.95
99
+ eps: 1.0e-08
100
+ weight_decay: 1.0e-08
101
+ stop_after_steps: 11500
102
+ is_resume: true
103
+ allow_model_only_resume: false
104
+ resume_accelerate_checkpoint: /home/liuyu/student-workspace/data/robotwin/common_start/qwen3vl-groot-robotwin-official150k-adamw8bit-mb5-ga4-seed42-20260810/checkpoints/steps_11000_accelerate
105
+ branch_parent_seed: 42
106
+ branch_rng_seed: 484370
107
+ model_rng_seed: 721927
108
+ sample_loss_weight_key: loss_is_importance_weight
109
+ model_rng_trace_key: loss_is_occurrence_id
110
+ loss_is:
111
+ candidate_factor: 3
112
+ seed: 484370
starVLA/dataloader/__init__.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import json
2
  import os
3
  from accelerate.logging import get_logger
@@ -100,10 +101,4 @@ def build_dataloader(cfg, dataset_py="lerobot_datasets_oxe"):
100
  manifest_path = output_dir / "valid_anchor_manifest.json"
101
  vla_dataset.valid_anchor_manifest.save(manifest_path)
102
  return vla_train_dataloader
103
- elif dataset_py == "vlm_datasets":
104
- from starVLA.dataloader.vlm_datasets import make_vlm_dataloader
105
-
106
- vlm_data_module = make_vlm_dataloader(cfg)
107
- vlm_train_dataloader = vlm_data_module["train_dataloader"]
108
-
109
- return vlm_train_dataloader
 
1
+ # Modified in the ReTime research artifact; see NOTICE.
2
  import json
3
  import os
4
  from accelerate.logging import get_logger
 
101
  manifest_path = output_dir / "valid_anchor_manifest.json"
102
  vla_dataset.valid_anchor_manifest.save(manifest_path)
103
  return vla_train_dataloader
104
+ raise ValueError(f"Unsupported dataset loader: {dataset_py}")
 
 
 
 
 
 
starVLA/dataloader/gr00t_lerobot/data_config.py CHANGED
@@ -1,3 +1,4 @@
 
1
  # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
  # SPDX-License-Identifier: Apache-2.0
3
  #
@@ -854,7 +855,7 @@ class AgilexDataConfig:
854
  ]
855
  action_keys = [
856
  "action.left_joints",
857
- "action.right_joints",#@JinhuiYE this order is different from Dataset
858
  "action.left_gripper",
859
  "action.right_gripper",
860
  ]
@@ -934,7 +935,7 @@ class AgilexData50Config:
934
  ]
935
  action_keys = [
936
  "action.left_joints",
937
- "action.right_joints", # @JinhuiYE this order is different from Dataset
938
  "action.left_gripper",
939
  "action.right_gripper",
940
  ]
 
1
+ # Modified in the ReTime research artifact; see NOTICE.
2
  # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
  # SPDX-License-Identifier: Apache-2.0
4
  #
 
855
  ]
856
  action_keys = [
857
  "action.left_joints",
858
+ "action.right_joints", # This order differs from the dataset schema.
859
  "action.left_gripper",
860
  "action.right_gripper",
861
  ]
 
935
  ]
936
  action_keys = [
937
  "action.left_joints",
938
+ "action.right_joints", # This order differs from the dataset schema.
939
  "action.left_gripper",
940
  "action.right_gripper",
941
  ]
starVLA/dataloader/gr00t_lerobot/datasets.py CHANGED
@@ -1,3 +1,4 @@
 
1
  # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
  # SPDX-License-Identifier: Apache-2.0
3
  #
@@ -55,9 +56,11 @@ from starVLA.dataloader.gr00t_lerobot.transform.state_action import StateActionT
55
  from starVLA.dataloader.infoflow_sampler import (
56
  LogicalDraw,
57
  ValidAnchorManifest,
 
58
  validate_dataset_content_inventory,
59
  )
60
  from starVLA.dataloader.temporal_proposal import TemporalProposal
 
61
 
62
  from functools import partial
63
  from typing import Tuple, List
@@ -152,7 +155,7 @@ def calculate_dataset_statistics(parquet_paths: list[Path]) -> dict:
152
 
153
  def _normalize_action_mode(mode: str) -> str:
154
  """Normalize action mode names to {abs, delta, rel}."""
155
- # @gaoning plz move this, we want dataloader to be independent of the action mode logic, we can move this to transform or a separate utils tool to handle lerobot dataset
156
  mode = str(mode).lower()
157
  if mode in {"absolute", "raw"}:
158
  mode = "abs"
@@ -390,6 +393,17 @@ def _get_action_col_slices(
390
  return action_col_slices
391
 
392
 
 
 
 
 
 
 
 
 
 
 
 
393
  def calculate_delta_action_statistics(
394
  parquet_paths: list[Path],
395
  lerobot_modality_meta: "LeRobotModalityMetadata",
@@ -504,7 +518,8 @@ def calculate_rel_action_statistics(
504
  Calculate action statistics using rel mode.
505
 
506
  Rule:
507
- - For all t: a_t - s_0
 
508
 
509
  Mapping rule (only two cases):
510
  1) Use explicit action_mode_state_map if provided.
@@ -563,7 +578,13 @@ def calculate_rel_action_statistics(
563
  if action_part_chunk.shape[1] != state_chunk.shape[1]:
564
  raise ValueError(f"Action/state dim mismatch for {action_col}:{a_slice}")
565
 
566
- out = action_part_chunk - state_chunk[0]
 
 
 
 
 
 
567
  action_chunk_full[:, a_slice[0] : a_slice[1]] = out
568
 
569
  accum[action_col].append(action_chunk_full)
@@ -1005,9 +1026,7 @@ class LeRobotSingleDataset(Dataset):
1005
  video_key = str(col)[len("videos/") : -len("/from_timestamp")]
1006
  from_timestamps[video_key] = float(value)
1007
 
1008
- # TODO auto map key
1009
  # Collect video file indices for each video key
1010
- #已修改的lerobotv3.0的视频索引(提取视频和文件的索引)
1011
  video_file_indices = {}
1012
  for col in timestamp_cols:
1013
  video_key = str(col)[len("videos/") : -len("/from_timestamp")]
@@ -1303,6 +1322,16 @@ class LeRobotSingleDataset(Dataset):
1303
  self.data_cfg.get("action_mode_state_map", {}) or {}
1304
  )
1305
 
 
 
 
 
 
 
 
 
 
 
1306
  def _infer_state_key_for_action(self, action_key: str) -> str | None:
1307
  if action_key in self._action_mode_state_map:
1308
  return self._action_mode_state_map[action_key]
@@ -1341,13 +1370,19 @@ class LeRobotSingleDataset(Dataset):
1341
  )
1342
 
1343
  state0 = state_values[0]
 
1344
  if self._action_mode == "delta":
1345
  out = action_values.copy()
 
 
 
 
 
1346
  if len(out) > 1:
1347
  out[1:] = action_values[1:] - action_values[:-1]
1348
  out[0] = action_values[0] - state0
1349
  elif self._action_mode == "rel":
1350
- out = action_values - state0
1351
  else:
1352
  out = action_values
1353
 
@@ -1523,7 +1558,7 @@ class LeRobotSingleDataset(Dataset):
1523
  data = {}
1524
  # Get the data for all modalities # just for action base data
1525
  self.curr_traj_data = self.get_trajectory_data(trajectory_id)
1526
- # TODO @JinhuiYE The logic below is poorly implemented. Data reading should be directly based on curr_traj_data.
1527
  for modality in self.modality_keys:
1528
  # Get the data corresponding to each key in the modality
1529
  for key in self.modality_keys[modality]:
@@ -1660,8 +1695,6 @@ class LeRobotSingleDataset(Dataset):
1660
  episode_meta = self.trajectory_ids_to_metadata[trajectory_id]
1661
 
1662
  video_file_indices = episode_meta.get("videos/file_indices", {})
1663
- # print(f"{video_file_indices=}")
1664
- #已修改的lerobotv3.0的视频索引
1665
  if original_key in video_file_indices:
1666
  video_chunk_index = video_file_indices[original_key]["chunk_index"]
1667
  video_file_index = video_file_indices[original_key]["file_index"]
@@ -2546,35 +2579,21 @@ class LeRobotMixtureDataset(Dataset):
2546
 
2547
  dataset_provenance = None
2548
  provenance_path = self.data_cfg.get("dataset_provenance_path") if self.data_cfg else None
2549
- if not provenance_path:
2550
- raise ValueError(
2551
- "traceable_sampling requires dataset_provenance_path so the frozen support "
2552
- "is bound to exact dataset revisions"
2553
- )
2554
- provenance_path = Path(provenance_path)
2555
- with open(provenance_path, "r", encoding="utf-8") as provenance_file:
2556
- provenance_payload = json.load(provenance_file)
2557
- provenance_bytes = provenance_path.read_bytes()
2558
- dataset_provenance = {
2559
- "sha256": hashlib.sha256(provenance_bytes).hexdigest(),
2560
- "payload": provenance_payload,
2561
- }
2562
 
2563
  content_fingerprint_path = (
2564
  self.data_cfg.get("dataset_content_fingerprint_path") if self.data_cfg else None
2565
  )
2566
- if not content_fingerprint_path:
2567
- raise ValueError(
2568
- "traceable_sampling requires dataset_content_fingerprint_path; generate the "
2569
- "full content hashes once before launching distributed training"
2570
- )
2571
- content_fingerprint_path = Path(content_fingerprint_path)
2572
- with open(content_fingerprint_path, "r", encoding="utf-8") as fingerprint_file:
2573
- content_fingerprints = json.load(fingerprint_file)
2574
- content_fingerprint_artifact_sha256 = hashlib.sha256(
2575
- content_fingerprint_path.read_bytes()
2576
- ).hexdigest()
2577
-
2578
  dataset_identities = []
2579
  for dataset in self.datasets:
2580
  try:
@@ -2592,9 +2611,9 @@ class LeRobotMixtureDataset(Dataset):
2592
  "modality": dataset._lerobot_modality_meta.model_dump(mode="json"),
2593
  "transforms": serialize_transforms(dataset),
2594
  "content": {
2595
- "sha256": content_fingerprint["sha256"],
2596
  "file_count": content_fingerprint["file_count"],
2597
  "total_bytes": content_fingerprint["total_bytes"],
 
2598
  },
2599
  "delta_indices": {
2600
  key: value.tolist() for key, value in dataset.delta_indices.items()
@@ -2608,7 +2627,6 @@ class LeRobotMixtureDataset(Dataset):
2608
  "balance_trajectory_weights": self.balance_trajectory_weights,
2609
  "excluded_episodes": excluded_episodes,
2610
  "dataset_provenance": dataset_provenance,
2611
- "content_fingerprint_artifact_sha256": content_fingerprint_artifact_sha256,
2612
  "datasets": dataset_identities,
2613
  }
2614
  identity = json.loads(json.dumps(identity, sort_keys=True, default=str))
@@ -2630,6 +2648,13 @@ class LeRobotMixtureDataset(Dataset):
2630
  frozen_identity = json.loads(json.dumps(frozen_manifest.identity))
2631
  current_identity = json.loads(json.dumps(current_manifest.identity))
2632
  for identity in (frozen_identity, current_identity):
 
 
 
 
 
 
 
2633
  data_config = identity.get("data_config", {})
2634
  statistics_path = data_config.get("precomputed_statistics_path")
2635
  if statistics_path:
@@ -2739,7 +2764,7 @@ class LeRobotMixtureDataset(Dataset):
2739
 
2740
  dataset, trajectory_id, step = self.sample_step(index)
2741
  # If dataset has no physical videos (e.g., image frames in parquet
2742
- # for VLA-Arena), do not gate sampling on mp4 existence.
2743
  total_videos = int(dataset.lerobot_info_meta.get("total_videos", 0))
2744
  if total_videos == 0:
2745
  break
 
1
+ # Modified in the ReTime research artifact; see NOTICE.
2
  # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
  # SPDX-License-Identifier: Apache-2.0
4
  #
 
56
  from starVLA.dataloader.infoflow_sampler import (
57
  LogicalDraw,
58
  ValidAnchorManifest,
59
+ compute_dataset_content_fingerprint,
60
  validate_dataset_content_inventory,
61
  )
62
  from starVLA.dataloader.temporal_proposal import TemporalProposal
63
+ from starVLA.dataloader.relative_pose import relative_values, rotation_type_is_quaternion
64
 
65
  from functools import partial
66
  from typing import Tuple, List
 
155
 
156
  def _normalize_action_mode(mode: str) -> str:
157
  """Normalize action mode names to {abs, delta, rel}."""
158
+ # Keep action-mode logic isolated from the generic dataset path.
159
  mode = str(mode).lower()
160
  if mode in {"absolute", "raw"}:
161
  mode = "abs"
 
393
  return action_col_slices
394
 
395
 
396
+ def _action_slice_is_quaternion(
397
+ lerobot_modality_meta: "LeRobotModalityMetadata",
398
+ action_slice: tuple[int, int],
399
+ ) -> bool:
400
+ start, end = action_slice
401
+ for cfg in lerobot_modality_meta.action.values():
402
+ if cfg.start == start and cfg.end == end:
403
+ return rotation_type_is_quaternion(cfg.rotation_type)
404
+ return False
405
+
406
+
407
  def calculate_delta_action_statistics(
408
  parquet_paths: list[Path],
409
  lerobot_modality_meta: "LeRobotModalityMetadata",
 
518
  Calculate action statistics using rel mode.
519
 
520
  Rule:
521
+ - Positions / scalars: a_t - s_0
522
+ - Quaternion keys: q_rel = inv(q_s0) ⊗ q_a_t (still a unit quaternion)
523
 
524
  Mapping rule (only two cases):
525
  1) Use explicit action_mode_state_map if provided.
 
578
  if action_part_chunk.shape[1] != state_chunk.shape[1]:
579
  raise ValueError(f"Action/state dim mismatch for {action_col}:{a_slice}")
580
 
581
+ out = relative_values(
582
+ action_part_chunk,
583
+ state_chunk[0],
584
+ is_quaternion=_action_slice_is_quaternion(
585
+ lerobot_modality_meta, a_slice
586
+ ),
587
+ )
588
  action_chunk_full[:, a_slice[0] : a_slice[1]] = out
589
 
590
  accum[action_col].append(action_chunk_full)
 
1026
  video_key = str(col)[len("videos/") : -len("/from_timestamp")]
1027
  from_timestamps[video_key] = float(value)
1028
 
 
1029
  # Collect video file indices for each video key
 
1030
  video_file_indices = {}
1031
  for col in timestamp_cols:
1032
  video_key = str(col)[len("videos/") : -len("/from_timestamp")]
 
1322
  self.data_cfg.get("action_mode_state_map", {}) or {}
1323
  )
1324
 
1325
+ def _action_key_is_quaternion(self, action_key: str) -> bool:
1326
+ if not action_key.startswith("action."):
1327
+ return False
1328
+ subkey = action_key.replace("action.", "", 1)
1329
+ try:
1330
+ cfg = self._lerobot_modality_meta.action[subkey]
1331
+ except (AttributeError, KeyError):
1332
+ return False
1333
+ return rotation_type_is_quaternion(cfg.rotation_type)
1334
+
1335
  def _infer_state_key_for_action(self, action_key: str) -> str | None:
1336
  if action_key in self._action_mode_state_map:
1337
  return self._action_mode_state_map[action_key]
 
1370
  )
1371
 
1372
  state0 = state_values[0]
1373
+ is_quaternion = self._action_key_is_quaternion(action_key)
1374
  if self._action_mode == "delta":
1375
  out = action_values.copy()
1376
+ if is_quaternion:
1377
+ raise ValueError(
1378
+ f"action_mode=delta is not defined for quaternion key {action_key}; "
1379
+ "use rel (SE(3) relative to the current pose) or abs."
1380
+ )
1381
  if len(out) > 1:
1382
  out[1:] = action_values[1:] - action_values[:-1]
1383
  out[0] = action_values[0] - state0
1384
  elif self._action_mode == "rel":
1385
+ out = relative_values(action_values, state0, is_quaternion=is_quaternion)
1386
  else:
1387
  out = action_values
1388
 
 
1558
  data = {}
1559
  # Get the data for all modalities # just for action base data
1560
  self.curr_traj_data = self.get_trajectory_data(trajectory_id)
1561
+ # Read the requested trajectory from the active trajectory table.
1562
  for modality in self.modality_keys:
1563
  # Get the data corresponding to each key in the modality
1564
  for key in self.modality_keys[modality]:
 
1695
  episode_meta = self.trajectory_ids_to_metadata[trajectory_id]
1696
 
1697
  video_file_indices = episode_meta.get("videos/file_indices", {})
 
 
1698
  if original_key in video_file_indices:
1699
  video_chunk_index = video_file_indices[original_key]["chunk_index"]
1700
  video_file_index = video_file_indices[original_key]["file_index"]
 
2579
 
2580
  dataset_provenance = None
2581
  provenance_path = self.data_cfg.get("dataset_provenance_path") if self.data_cfg else None
2582
+ if provenance_path:
2583
+ with open(provenance_path, "r", encoding="utf-8") as provenance_file:
2584
+ dataset_provenance = json.load(provenance_file)
 
 
 
 
 
 
 
 
 
 
2585
 
2586
  content_fingerprint_path = (
2587
  self.data_cfg.get("dataset_content_fingerprint_path") if self.data_cfg else None
2588
  )
2589
+ if content_fingerprint_path:
2590
+ with open(content_fingerprint_path, "r", encoding="utf-8") as fingerprint_file:
2591
+ content_fingerprints = json.load(fingerprint_file)
2592
+ else:
2593
+ content_fingerprints = {"datasets": {
2594
+ dataset.dataset_name: compute_dataset_content_fingerprint(dataset.dataset_path)
2595
+ for dataset in self.datasets
2596
+ }}
 
 
 
 
2597
  dataset_identities = []
2598
  for dataset in self.datasets:
2599
  try:
 
2611
  "modality": dataset._lerobot_modality_meta.model_dump(mode="json"),
2612
  "transforms": serialize_transforms(dataset),
2613
  "content": {
 
2614
  "file_count": content_fingerprint["file_count"],
2615
  "total_bytes": content_fingerprint["total_bytes"],
2616
+ "files": content_fingerprint["files"],
2617
  },
2618
  "delta_indices": {
2619
  key: value.tolist() for key, value in dataset.delta_indices.items()
 
2627
  "balance_trajectory_weights": self.balance_trajectory_weights,
2628
  "excluded_episodes": excluded_episodes,
2629
  "dataset_provenance": dataset_provenance,
 
2630
  "datasets": dataset_identities,
2631
  }
2632
  identity = json.loads(json.dumps(identity, sort_keys=True, default=str))
 
2648
  frozen_identity = json.loads(json.dumps(frozen_manifest.identity))
2649
  current_identity = json.loads(json.dumps(current_manifest.identity))
2650
  for identity in (frozen_identity, current_identity):
2651
+ provenance = identity.get("dataset_provenance")
2652
+ if isinstance(provenance, dict) and "payload" in provenance:
2653
+ identity["dataset_provenance"] = provenance["payload"]
2654
+ identity.pop("content_fingerprint_artifact_sha256", None)
2655
+ for dataset_identity in identity.get("datasets", []):
2656
+ content = dataset_identity.get("content", {})
2657
+ content.pop("sha256", None)
2658
  data_config = identity.get("data_config", {})
2659
  statistics_path = data_config.get("precomputed_statistics_path")
2660
  if statistics_path:
 
2764
 
2765
  dataset, trajectory_id, step = self.sample_step(index)
2766
  # If dataset has no physical videos (e.g., image frames in parquet
2767
+ # for VLA-Arena), sampling does not require mp4 files.
2768
  total_videos = int(dataset.lerobot_info_meta.get("total_videos", 0))
2769
  if total_videos == 0:
2770
  break
starVLA/dataloader/gr00t_lerobot/transform/state_action.py CHANGED
@@ -1,3 +1,4 @@
 
1
  # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
  # SPDX-License-Identifier: Apache-2.0
3
  #
 
1
+ # Modified in the ReTime research artifact; see NOTICE.
2
  # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
  # SPDX-License-Identifier: Apache-2.0
4
  #
starVLA/dataloader/infoflow_sampler.py CHANGED
@@ -1,340 +1,6 @@
1
- """Deterministic hierarchical sampling primitives for InfoFlow experiments."""
2
 
3
- from __future__ import annotations
 
4
 
5
- from dataclasses import asdict, dataclass
6
- import hashlib
7
- import json
8
- import os
9
- from pathlib import Path
10
- from typing import Any, Iterator, Sequence
11
-
12
- import numpy as np
13
- from torch.utils.data import Sampler
14
-
15
-
16
- def _identity_files(dataset_root: Path) -> list[Path]:
17
- files = list((dataset_root / "data").rglob("*.parquet"))
18
- files.extend((dataset_root / "videos").rglob("*.mp4"))
19
- meta_root = dataset_root / "meta"
20
- if meta_root.exists():
21
- for pattern in ("*.json", "*.jsonl", "*.parquet"):
22
- files.extend(meta_root.rglob(pattern))
23
- return sorted(set(files), key=lambda path: path.relative_to(dataset_root).as_posix())
24
-
25
-
26
- def compute_dataset_content_fingerprint(dataset_root: str | Path) -> dict[str, Any]:
27
- """Hash immutable LeRobot data, video, and task/episode metadata files once."""
28
- root = Path(dataset_root)
29
- digest = hashlib.sha256()
30
- entries = []
31
- total_bytes = 0
32
- for file_path in _identity_files(root):
33
- relative_path = file_path.relative_to(root).as_posix()
34
- file_size = file_path.stat().st_size
35
- entries.append({"path": relative_path, "bytes": file_size})
36
- total_bytes += file_size
37
- digest.update(relative_path.encode("utf-8"))
38
- digest.update(str(file_size).encode("ascii"))
39
- with open(file_path, "rb") as data_file:
40
- for chunk in iter(lambda: data_file.read(4 * 1024 * 1024), b""):
41
- digest.update(chunk)
42
- return {
43
- "sha256": digest.hexdigest(),
44
- "file_count": len(entries),
45
- "total_bytes": total_bytes,
46
- "files": entries,
47
- }
48
-
49
-
50
- def validate_dataset_content_inventory(
51
- dataset_root: str | Path, fingerprint: dict[str, Any]
52
- ) -> None:
53
- """Cheap per-rank check; full content hashing belongs to fingerprint generation."""
54
- root = Path(dataset_root)
55
- current_entries = [
56
- {
57
- "path": path.relative_to(root).as_posix(),
58
- "bytes": path.stat().st_size,
59
- }
60
- for path in _identity_files(root)
61
- ]
62
- if current_entries != fingerprint.get("files"):
63
- raise RuntimeError(
64
- f"Dataset file inventory differs from its content fingerprint: {root}"
65
- )
66
- if len(current_entries) != fingerprint.get("file_count"):
67
- raise RuntimeError("Dataset content fingerprint has an inconsistent file count")
68
- if sum(entry["bytes"] for entry in current_entries) != fingerprint.get("total_bytes"):
69
- raise RuntimeError("Dataset content fingerprint has an inconsistent byte count")
70
-
71
-
72
- def _normalize_probabilities(values: Sequence[float], name: str) -> tuple[float, ...]:
73
- probabilities = np.asarray(values, dtype=np.float64)
74
- if probabilities.ndim != 1 or probabilities.size == 0:
75
- raise ValueError(f"{name} must be a non-empty one-dimensional sequence")
76
- if not np.all(np.isfinite(probabilities)) or np.any(probabilities <= 0):
77
- raise ValueError(f"{name} must contain finite, strictly positive values")
78
- probabilities /= probabilities.sum(dtype=np.float64)
79
- return tuple(float(value) for value in probabilities)
80
-
81
-
82
- def _stateless_seed(seed: int, epoch: int, global_draw_id: int) -> int:
83
- payload = f"{int(seed)}:{int(epoch)}:{int(global_draw_id)}".encode("utf-8")
84
- return int.from_bytes(hashlib.sha256(payload).digest()[:8], "little", signed=False)
85
-
86
-
87
- @dataclass(frozen=True)
88
- class EpisodeSupport:
89
- trajectory_index: int
90
- trajectory_id: int | str
91
- anchors: tuple[int, ...]
92
-
93
- def __post_init__(self) -> None:
94
- if not self.anchors:
95
- raise ValueError("episode support must contain at least one anchor")
96
- if len(set(self.anchors)) != len(self.anchors):
97
- raise ValueError("episode anchors must be unique")
98
-
99
-
100
- @dataclass(frozen=True)
101
- class DatasetSupport:
102
- name: str
103
- probability: float
104
- episodes: tuple[EpisodeSupport, ...]
105
- episode_probabilities: tuple[float, ...]
106
-
107
- def __post_init__(self) -> None:
108
- if not self.episodes:
109
- raise ValueError(f"dataset {self.name!r} has no valid episodes")
110
- if len(self.episodes) != len(self.episode_probabilities):
111
- raise ValueError("episode support and probability lengths differ")
112
- normalized = _normalize_probabilities(self.episode_probabilities, "episode_probabilities")
113
- if not np.allclose(normalized, self.episode_probabilities, rtol=0, atol=1e-12):
114
- raise ValueError("episode probabilities must already sum to one")
115
-
116
-
117
- @dataclass(frozen=True)
118
- class LogicalDraw:
119
- epoch: int
120
- global_draw_id: int
121
-
122
- def __post_init__(self) -> None:
123
- if self.epoch < 0 or self.global_draw_id < 0:
124
- raise ValueError("epoch and global_draw_id must be non-negative")
125
-
126
-
127
- @dataclass(frozen=True)
128
- class SampleTrace:
129
- manifest_sha256: str
130
- epoch: int
131
- global_draw_id: int
132
- dataset_index: int
133
- dataset_name: str
134
- trajectory_index: int
135
- trajectory_id: int | str
136
- anchor_index: int
137
- base_index: int
138
- baseline_probability: float
139
- proposal_sha256: str | None = None
140
- proposal_probability: float | None = None
141
- importance_weight: float = 1.0
142
-
143
- def to_dict(self) -> dict[str, Any]:
144
- return asdict(self)
145
-
146
-
147
- class ValidAnchorManifest:
148
- schema_version = 1
149
-
150
- def __init__(self, datasets: Sequence[DatasetSupport], identity: dict[str, Any] | None = None):
151
- if not datasets:
152
- raise ValueError("manifest must contain at least one dataset")
153
- dataset_probabilities = _normalize_probabilities(
154
- [dataset.probability for dataset in datasets], "dataset_probabilities"
155
- )
156
- if not np.allclose(
157
- dataset_probabilities,
158
- [dataset.probability for dataset in datasets],
159
- rtol=0,
160
- atol=1e-12,
161
- ):
162
- raise ValueError("dataset probabilities must already sum to one")
163
- self.datasets = tuple(datasets)
164
- self.identity = identity or {}
165
- self._dataset_probabilities = np.asarray(
166
- [dataset.probability for dataset in self.datasets], dtype=np.float64
167
- )
168
- self._episode_probabilities = tuple(
169
- np.asarray(dataset.episode_probabilities, dtype=np.float64)
170
- for dataset in self.datasets
171
- )
172
- canonical = json.dumps(self.to_dict(include_hash=False), sort_keys=True, separators=(",", ":"))
173
- self.sha256 = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
174
-
175
- @classmethod
176
- def from_hierarchy(
177
- cls,
178
- dataset_names: Sequence[str],
179
- dataset_weights: Sequence[float],
180
- episodes: Sequence[Sequence[tuple[int | str, Sequence[int], float]]],
181
- identity: dict[str, Any] | None = None,
182
- ) -> "ValidAnchorManifest":
183
- if len(dataset_names) != len(dataset_weights) or len(dataset_names) != len(episodes):
184
- raise ValueError("dataset names, weights, and episode supports must have equal lengths")
185
- dataset_probabilities = _normalize_probabilities(dataset_weights, "dataset_weights")
186
- supports = []
187
- for dataset_index, (name, dataset_episodes) in enumerate(zip(dataset_names, episodes)):
188
- if not dataset_episodes:
189
- raise ValueError(f"dataset {name!r} has no valid episodes")
190
- episode_probabilities = _normalize_probabilities(
191
- [entry[2] for entry in dataset_episodes], f"episode_weights[{dataset_index}]"
192
- )
193
- episode_supports = tuple(
194
- EpisodeSupport(
195
- trajectory_index=trajectory_index,
196
- trajectory_id=trajectory_id,
197
- anchors=tuple(int(anchor) for anchor in anchors),
198
- )
199
- for trajectory_index, (trajectory_id, anchors, _) in enumerate(dataset_episodes)
200
- )
201
- supports.append(
202
- DatasetSupport(
203
- name=str(name),
204
- probability=dataset_probabilities[dataset_index],
205
- episodes=episode_supports,
206
- episode_probabilities=episode_probabilities,
207
- )
208
- )
209
- return cls(supports, identity=identity)
210
-
211
- @classmethod
212
- def from_dict(cls, payload: dict[str, Any]) -> "ValidAnchorManifest":
213
- datasets = []
214
- for dataset_payload in payload["datasets"]:
215
- episodes = tuple(
216
- EpisodeSupport(
217
- trajectory_index=int(episode["trajectory_index"]),
218
- trajectory_id=episode["trajectory_id"],
219
- anchors=tuple(int(anchor) for anchor in episode["anchors"]),
220
- )
221
- for episode in dataset_payload["episodes"]
222
- )
223
- datasets.append(
224
- DatasetSupport(
225
- name=str(dataset_payload["name"]),
226
- probability=float(dataset_payload["probability"]),
227
- episodes=episodes,
228
- episode_probabilities=tuple(
229
- float(value) for value in dataset_payload["episode_probabilities"]
230
- ),
231
- )
232
- )
233
- manifest = cls(datasets, identity=payload.get("identity", {}))
234
- return manifest
235
-
236
- @classmethod
237
- def load(cls, path: str | Path) -> "ValidAnchorManifest":
238
- with open(path, "r", encoding="utf-8") as manifest_file:
239
- return cls.from_dict(json.load(manifest_file))
240
-
241
- def save(self, path: str | Path) -> None:
242
- destination = Path(path)
243
- destination.parent.mkdir(parents=True, exist_ok=True)
244
- temporary = destination.with_suffix(destination.suffix + ".tmp")
245
- with open(temporary, "w", encoding="utf-8") as manifest_file:
246
- json.dump(self.to_dict(), manifest_file, indent=2)
247
- os.replace(temporary, destination)
248
-
249
- def sample(self, seed: int, draw: LogicalDraw) -> SampleTrace:
250
- rng = np.random.default_rng(_stateless_seed(seed, draw.epoch, draw.global_draw_id))
251
- dataset_index = int(rng.choice(len(self.datasets), p=self._dataset_probabilities))
252
- dataset = self.datasets[dataset_index]
253
- trajectory_index = int(
254
- rng.choice(len(dataset.episodes), p=self._episode_probabilities[dataset_index])
255
- )
256
- episode = dataset.episodes[trajectory_index]
257
- anchor_index = int(rng.integers(len(episode.anchors)))
258
- baseline_probability = (
259
- dataset.probability
260
- * dataset.episode_probabilities[trajectory_index]
261
- / len(episode.anchors)
262
- )
263
- return SampleTrace(
264
- manifest_sha256=self.sha256,
265
- epoch=int(draw.epoch),
266
- global_draw_id=int(draw.global_draw_id),
267
- dataset_index=dataset_index,
268
- dataset_name=dataset.name,
269
- trajectory_index=episode.trajectory_index,
270
- trajectory_id=episode.trajectory_id,
271
- anchor_index=anchor_index,
272
- base_index=episode.anchors[anchor_index],
273
- baseline_probability=float(baseline_probability),
274
- )
275
-
276
- def to_dict(self, include_hash: bool = True) -> dict[str, Any]:
277
- payload = {
278
- "schema_version": self.schema_version,
279
- "identity": self.identity,
280
- "datasets": [asdict(dataset) for dataset in self.datasets],
281
- }
282
- if include_hash:
283
- payload["sha256"] = self.sha256
284
- return payload
285
-
286
-
287
- class StatelessDrawSampler(Sampler[LogicalDraw]):
288
- """Yield rank-disjoint logical draw ids; sampling itself stays inside the dataset."""
289
-
290
- def __init__(
291
- self,
292
- num_local_draws: int,
293
- rank: int = 0,
294
- world_size: int = 1,
295
- start_global_draw_id: int = 0,
296
- ) -> None:
297
- if num_local_draws < 0:
298
- raise ValueError("num_local_draws must be non-negative")
299
- if world_size <= 0 or rank < 0 or rank >= world_size:
300
- raise ValueError("rank must satisfy 0 <= rank < world_size")
301
- self.num_local_draws = int(num_local_draws)
302
- self.rank = int(rank)
303
- self.world_size = int(world_size)
304
- self.start_global_draw_id = int(start_global_draw_id)
305
- if self.start_global_draw_id < 0:
306
- raise ValueError("start_global_draw_id must be non-negative")
307
- self.epoch = 0
308
- self._resume_epoch = 0
309
-
310
- def set_resume_position(self, epoch: int, start_global_draw_id: int) -> None:
311
- """Resume at an inclusive global draw cursor within one epoch."""
312
- if epoch < 0 or start_global_draw_id < 0:
313
- raise ValueError("resume epoch and draw id must be non-negative")
314
- self.epoch = int(epoch)
315
- self._resume_epoch = int(epoch)
316
- self.start_global_draw_id = int(start_global_draw_id)
317
-
318
- def set_epoch(self, epoch: int) -> None:
319
- if epoch < 0:
320
- raise ValueError("epoch must be non-negative")
321
- self.epoch = int(epoch)
322
- if self.epoch != self._resume_epoch:
323
- self.start_global_draw_id = 0
324
-
325
- def _first_local_draw_id(self) -> int:
326
- return max(
327
- 0,
328
- (self.start_global_draw_id - self.rank + self.world_size - 1)
329
- // self.world_size,
330
- )
331
-
332
- def __iter__(self) -> Iterator[LogicalDraw]:
333
- for local_draw_id in range(self._first_local_draw_id(), self.num_local_draws):
334
- yield LogicalDraw(
335
- epoch=self.epoch,
336
- global_draw_id=local_draw_id * self.world_size + self.rank,
337
- )
338
-
339
- def __len__(self) -> int:
340
- return max(0, self.num_local_draws - self._first_local_draw_id())
 
1
+ """Compatibility alias for the ReTime plugin; preserves legacy imports."""
2
 
3
+ from importlib import import_module
4
+ import sys
5
 
6
+ sys.modules[__name__] = import_module('retime.sampler')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
starVLA/dataloader/lerobot_datasets.py CHANGED
@@ -1,7 +1,6 @@
 
1
  # Copyright 2025 NVIDIA Corp. and affiliates. All rights reserved.
2
- # Modified by [Fangjing Wang/ SUST University] in [2025].
3
  # Modification: [return raw data and suport multi-dataset mixture].
4
- # Modified by [Jinhui YE/ HKUST University] in [2025].
5
  # Modification: [suport topdowm processing, suport param from config].
6
 
7
  import logging
@@ -160,4 +159,4 @@ if __name__ == "__main__":
160
  if count > 3:
161
  break
162
  count += 1
163
- pass
 
1
+ # Modified in the ReTime research artifact; see NOTICE.
2
  # Copyright 2025 NVIDIA Corp. and affiliates. All rights reserved.
 
3
  # Modification: [return raw data and suport multi-dataset mixture].
 
4
  # Modification: [suport topdowm processing, suport param from config].
5
 
6
  import logging
 
159
  if count > 3:
160
  break
161
  count += 1
162
+ pass
starVLA/dataloader/relative_pose.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """SE(3) relative pose helpers for wxyz quaternions.
2
+
3
+ ``action_mode: rel`` must not subtract quaternion components. Positions are
4
+ ``xyz_a - xyz_s0``; rotations are ``q_rel = inv(q_s0) ⊗ q_a``. The relative
5
+ quaternion is still a unit quaternion, so later rotation-6D conversion stays
6
+ valid with ``absolute: true`` in modality metadata.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import numpy as np
12
+
13
+ _QUATERNION_DIM = 4
14
+
15
+
16
+ def _as_float(values: np.ndarray) -> np.ndarray:
17
+ return np.asarray(values, dtype=np.float32)
18
+
19
+
20
+ def quat_conjugate(quaternion: np.ndarray) -> np.ndarray:
21
+ """Inverse of a unit wxyz quaternion (conjugate)."""
22
+ conjugated = np.array(_as_float(quaternion), copy=True)
23
+ conjugated[..., 1:] *= -1
24
+ return conjugated
25
+
26
+
27
+ def quat_multiply(left: np.ndarray, right: np.ndarray) -> np.ndarray:
28
+ """Hamilton product of wxyz quaternions, broadcasting on leading axes."""
29
+ left_q, right_q = np.broadcast_arrays(_as_float(left), _as_float(right))
30
+ w1, x1, y1, z1 = left_q[..., 0], left_q[..., 1], left_q[..., 2], left_q[..., 3]
31
+ w2, x2, y2, z2 = right_q[..., 0], right_q[..., 1], right_q[..., 2], right_q[..., 3]
32
+ product = np.stack(
33
+ [
34
+ w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2,
35
+ w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2,
36
+ w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2,
37
+ w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2,
38
+ ],
39
+ axis=-1,
40
+ )
41
+ return _normalize_quaternion(product)
42
+
43
+
44
+ def _normalize_quaternion(quaternion: np.ndarray) -> np.ndarray:
45
+ norms = np.linalg.norm(quaternion, axis=-1, keepdims=True)
46
+ return quaternion / np.clip(norms, 1e-8, None)
47
+
48
+
49
+ def relative_quaternion(state_quaternion: np.ndarray, action_quaternion: np.ndarray) -> np.ndarray:
50
+ """Rotation taking ``state`` onto ``action``: ``inv(q_s) ⊗ q_a``."""
51
+ return quat_multiply(quat_conjugate(state_quaternion), action_quaternion)
52
+
53
+
54
+ def compose_quaternion(state_quaternion: np.ndarray, relative_quaternion_value: np.ndarray) -> np.ndarray:
55
+ """Apply a relative rotation: ``q_s ⊗ q_rel``."""
56
+ return quat_multiply(state_quaternion, relative_quaternion_value)
57
+
58
+
59
+ def relative_values(
60
+ action_values: np.ndarray,
61
+ state0: np.ndarray,
62
+ *,
63
+ is_quaternion: bool,
64
+ ) -> np.ndarray:
65
+ """Convert one action block into the ``rel`` representation."""
66
+ action = _as_float(action_values)
67
+ origin = _as_float(state0)
68
+ if is_quaternion:
69
+ if action.shape[-1] != _QUATERNION_DIM or origin.shape[-1] != _QUATERNION_DIM:
70
+ raise ValueError(
71
+ f"quaternion rel requires last dim {_QUATERNION_DIM}, "
72
+ f"got action {action.shape} state {origin.shape}"
73
+ )
74
+ return relative_quaternion(origin, action)
75
+ return action - origin
76
+
77
+
78
+ def compose_values(
79
+ relative_action: np.ndarray,
80
+ state0: np.ndarray,
81
+ *,
82
+ is_quaternion: bool,
83
+ ) -> np.ndarray:
84
+ """Invert :func:`relative_values`."""
85
+ relative = _as_float(relative_action)
86
+ origin = _as_float(state0)
87
+ if is_quaternion:
88
+ return compose_quaternion(origin, relative)
89
+ return relative + origin
90
+
91
+
92
+ def rotation_type_is_quaternion(rotation_type: object | None) -> bool:
93
+ if rotation_type is None:
94
+ return False
95
+ value = rotation_type.value if hasattr(rotation_type, "value") else str(rotation_type)
96
+ return value == "quaternion"
starVLA/dataloader/temporal_proposal.py CHANGED
@@ -1,229 +1,6 @@
1
- """Offline temporal proposals that preserve StarVLA's dataset/episode marginals."""
2
 
3
- from __future__ import annotations
 
4
 
5
- import hashlib
6
- import json
7
- import os
8
- from pathlib import Path
9
- from typing import Any, Sequence
10
-
11
- import numpy as np
12
-
13
- from starVLA.dataloader.infoflow_sampler import (
14
- LogicalDraw,
15
- SampleTrace,
16
- ValidAnchorManifest,
17
- _stateless_seed,
18
- )
19
-
20
-
21
- class TemporalProposal:
22
- schema_version = 1
23
- valid_score_kinds = {
24
- "synthetic",
25
- "random",
26
- "action_change",
27
- "residual",
28
- "likelihood",
29
- }
30
-
31
- def __init__(
32
- self,
33
- manifest: ValidAnchorManifest,
34
- alpha: float,
35
- anchor_probabilities: Sequence[Sequence[Sequence[float]]],
36
- score_kind: str,
37
- scorer_sha256: str,
38
- validation_certificate: dict[str, Any] | None = None,
39
- legacy_implementation_sha256: str | None = None,
40
- ) -> None:
41
- if not 0.0 <= alpha < 1.0:
42
- raise ValueError("alpha must satisfy 0 <= alpha < 1")
43
- if score_kind not in self.valid_score_kinds:
44
- raise ValueError(f"unsupported score_kind: {score_kind}")
45
- if score_kind == "likelihood" and not validation_certificate:
46
- raise ValueError("likelihood proposals require a validation certificate")
47
- if not scorer_sha256:
48
- raise ValueError("scorer_sha256 is required")
49
- if len(anchor_probabilities) != len(manifest.datasets):
50
- raise ValueError("proposal dataset count does not match the manifest")
51
-
52
- normalized_probabilities = []
53
- importance_weights = []
54
- normalized_masses = []
55
- for dataset_index, (dataset, dataset_probabilities) in enumerate(
56
- zip(manifest.datasets, anchor_probabilities)
57
- ):
58
- if len(dataset_probabilities) != len(dataset.episodes):
59
- raise ValueError(
60
- f"proposal episode count differs for dataset {dataset_index}"
61
- )
62
- normalized_dataset = []
63
- dataset_weights = []
64
- normalized_dataset_masses = []
65
- for episode_index, (episode, proposal_mass) in enumerate(
66
- zip(dataset.episodes, dataset_probabilities)
67
- ):
68
- mass = np.asarray(proposal_mass, dtype=np.float64)
69
- if mass.shape != (len(episode.anchors),):
70
- raise ValueError(
71
- f"proposal anchor count differs for dataset {dataset_index}, "
72
- f"episode {episode_index}"
73
- )
74
- if not np.all(np.isfinite(mass)) or np.any(mass < 0) or mass.sum() <= 0:
75
- raise ValueError("proposal masses must be finite, non-negative, and non-zero")
76
- residual_probability = mass / mass.sum(dtype=np.float64)
77
- baseline_probability = np.full(len(mass), 1.0 / len(mass), dtype=np.float64)
78
- safe_probability = (1.0 - alpha) * baseline_probability + alpha * residual_probability
79
- weights = baseline_probability / safe_probability
80
- if not np.isclose(safe_probability.sum(dtype=np.float64), 1.0, atol=1e-12):
81
- raise ValueError("safe temporal proposal does not sum to one")
82
- if np.max(weights) > 1.0 / (1.0 - alpha) + 1e-12:
83
- raise ValueError("importance weight exceeds the analytical safe-mixture bound")
84
- normalized_dataset.append(safe_probability)
85
- dataset_weights.append(weights)
86
- normalized_dataset_masses.append(mass.copy())
87
- normalized_probabilities.append(tuple(normalized_dataset))
88
- importance_weights.append(tuple(dataset_weights))
89
- normalized_masses.append(tuple(normalized_dataset_masses))
90
-
91
- self.manifest_sha256 = manifest.sha256
92
- self._manifest_support = manifest.to_dict(include_hash=False)["datasets"]
93
- # Schema-v1 proposals included a source-file hash in their content hash.
94
- # Preserve that value only when loading an existing artifact; it is not
95
- # compared with the current source and new proposals do not emit it.
96
- self.legacy_implementation_sha256 = legacy_implementation_sha256
97
- self.alpha = float(alpha)
98
- self.score_kind = score_kind
99
- self.scorer_sha256 = scorer_sha256
100
- self.validation_certificate = validation_certificate
101
- self.proposal_masses = tuple(normalized_masses)
102
- self.anchor_probabilities = tuple(normalized_probabilities)
103
- self.importance_weights = tuple(importance_weights)
104
- canonical = json.dumps(self.to_dict(include_hash=False), sort_keys=True, separators=(",", ":"))
105
- self.sha256 = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
106
-
107
- @classmethod
108
- def from_masses(
109
- cls,
110
- manifest: ValidAnchorManifest,
111
- alpha: float,
112
- masses: Sequence[Sequence[Sequence[float]]],
113
- score_kind: str = "synthetic",
114
- scorer_sha256: str = "synthetic-fixture",
115
- validation_certificate: dict[str, Any] | None = None,
116
- ) -> "TemporalProposal":
117
- return cls(
118
- manifest=manifest,
119
- alpha=alpha,
120
- anchor_probabilities=masses,
121
- score_kind=score_kind,
122
- scorer_sha256=scorer_sha256,
123
- validation_certificate=validation_certificate,
124
- )
125
-
126
- @classmethod
127
- def from_dict(
128
- cls, manifest: ValidAnchorManifest, payload: dict[str, Any]
129
- ) -> "TemporalProposal":
130
- if payload.get("schema_version") != cls.schema_version:
131
- raise ValueError(
132
- f"unsupported temporal proposal schema: {payload.get('schema_version')}"
133
- )
134
- proposal = cls(
135
- manifest=manifest,
136
- alpha=float(payload["alpha"]),
137
- anchor_probabilities=payload["proposal_masses"],
138
- score_kind=str(payload["score_kind"]),
139
- scorer_sha256=str(payload["scorer_sha256"]),
140
- validation_certificate=payload.get("validation_certificate"),
141
- legacy_implementation_sha256=payload.get("implementation_sha256"),
142
- )
143
- return proposal
144
-
145
- @classmethod
146
- def load(
147
- cls, path: str | Path, manifest: ValidAnchorManifest
148
- ) -> "TemporalProposal":
149
- with open(path, "r", encoding="utf-8") as proposal_file:
150
- return cls.from_dict(manifest, json.load(proposal_file))
151
-
152
- def save(self, path: str | Path) -> None:
153
- destination = Path(path)
154
- destination.parent.mkdir(parents=True, exist_ok=True)
155
- temporary = destination.with_suffix(destination.suffix + ".tmp")
156
- with open(temporary, "w", encoding="utf-8") as proposal_file:
157
- json.dump(self.to_dict(), proposal_file, indent=2)
158
- os.replace(temporary, destination)
159
-
160
- def sample(
161
- self, manifest: ValidAnchorManifest, seed: int, draw: LogicalDraw
162
- ) -> SampleTrace:
163
- if manifest.to_dict(include_hash=False)["datasets"] != self._manifest_support:
164
- raise ValueError("temporal proposal support differs from the active manifest")
165
- rng = np.random.default_rng(_stateless_seed(seed, draw.epoch, draw.global_draw_id))
166
- dataset_index = int(rng.choice(len(manifest.datasets), p=manifest._dataset_probabilities))
167
- dataset = manifest.datasets[dataset_index]
168
- trajectory_index = int(
169
- rng.choice(
170
- len(dataset.episodes), p=manifest._episode_probabilities[dataset_index]
171
- )
172
- )
173
- episode = dataset.episodes[trajectory_index]
174
- if self.alpha == 0.0:
175
- # Preserve the exact baseline RNG path, not merely its distribution.
176
- anchor_index = int(rng.integers(len(episode.anchors)))
177
- else:
178
- anchor_index = int(
179
- rng.choice(
180
- len(episode.anchors),
181
- p=self.anchor_probabilities[dataset_index][trajectory_index],
182
- )
183
- )
184
- baseline_probability = (
185
- dataset.probability
186
- * dataset.episode_probabilities[trajectory_index]
187
- / len(episode.anchors)
188
- )
189
- proposal_probability = (
190
- dataset.probability
191
- * dataset.episode_probabilities[trajectory_index]
192
- * self.anchor_probabilities[dataset_index][trajectory_index][anchor_index]
193
- )
194
- return SampleTrace(
195
- manifest_sha256=manifest.sha256,
196
- epoch=draw.epoch,
197
- global_draw_id=draw.global_draw_id,
198
- dataset_index=dataset_index,
199
- dataset_name=dataset.name,
200
- trajectory_index=episode.trajectory_index,
201
- trajectory_id=episode.trajectory_id,
202
- anchor_index=anchor_index,
203
- base_index=episode.anchors[anchor_index],
204
- baseline_probability=float(baseline_probability),
205
- proposal_sha256=self.sha256,
206
- proposal_probability=float(proposal_probability),
207
- importance_weight=float(
208
- self.importance_weights[dataset_index][trajectory_index][anchor_index]
209
- ),
210
- )
211
-
212
- def to_dict(self, include_hash: bool = True) -> dict[str, Any]:
213
- payload = {
214
- "schema_version": self.schema_version,
215
- "manifest_sha256": self.manifest_sha256,
216
- "alpha": self.alpha,
217
- "score_kind": self.score_kind,
218
- "scorer_sha256": self.scorer_sha256,
219
- "validation_certificate": self.validation_certificate,
220
- "proposal_masses": [
221
- [probabilities.tolist() for probabilities in dataset]
222
- for dataset in self.proposal_masses
223
- ],
224
- }
225
- if self.legacy_implementation_sha256 is not None:
226
- payload["implementation_sha256"] = self.legacy_implementation_sha256
227
- if include_hash:
228
- payload["sha256"] = self.sha256
229
- return payload
 
1
+ """Compatibility alias for the ReTime plugin; preserves legacy imports."""
2
 
3
+ from importlib import import_module
4
+ import sys
5
 
6
+ sys.modules[__name__] = import_module('retime.proposal')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
starVLA/model/framework/VLM4A/PI0.py ADDED
@@ -0,0 +1,653 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ import math
5
+ from pathlib import Path
6
+ from typing import Literal
7
+
8
+ import numpy as np
9
+ import torch
10
+ import torch.nn.functional as F
11
+ from starVLA.model.modules.vlm.openpi_transformers.gemma import modeling_gemma
12
+
13
+ from deployment.model_server.tools.image_tools import resize_with_pad, to_pil_preserve
14
+ from starVLA.model.framework.base_framework import baseframework
15
+ from starVLA.model.framework.share_tools import merge_framework_config
16
+ from starVLA.model.tools import FRAMEWORK_REGISTRY
17
+ from starVLA.model.modules.action_model.OpenPI_ActionHead import (
18
+ GemmaDims,
19
+ OpenPI0ActionHead,
20
+ OpenPIActionHeadBase,
21
+ make_att_2d_masks,
22
+ make_att_4d_masks,
23
+ )
24
+ from starVLA.model.modules.vlm.OpenPIPaliGemma import OpenPIPaliGemma
25
+ from starVLA.training.trainer_utils import initialize_overwatch
26
+
27
+ logger = initialize_overwatch(__name__)
28
+
29
+ IMAGE_KEYS = ("base_0_rgb", "left_wrist_0_rgb", "right_wrist_0_rgb")
30
+
31
+
32
+ @dataclass
33
+ class Observation:
34
+ images: dict[str, torch.Tensor]
35
+ image_masks: dict[str, torch.Tensor]
36
+ tokenized_prompt: torch.Tensor
37
+ tokenized_prompt_mask: torch.Tensor
38
+ state: torch.Tensor
39
+
40
+
41
+ @dataclass
42
+ class PI0DefaultConfig:
43
+ name: str = "PI0"
44
+ precision: Literal["bfloat16", "float32"] = "bfloat16"
45
+
46
+ action_dim: int = 32
47
+ action_horizon: int = 50
48
+ max_state_dim: int = 32
49
+ discrete_state_input: bool = False
50
+ max_token_len: int = 200
51
+ num_inference_steps: int = 10
52
+
53
+ image_resolution: list[int] = field(default_factory=lambda: [224, 224])
54
+ image_keys: list[str] = field(default_factory=lambda: list(IMAGE_KEYS))
55
+
56
+ tokenizer: dict = field(default_factory=lambda: {"model_path": "", "pad_state_value": -2.0})
57
+ paligemma: dict = field(default_factory=lambda: {"model_name_or_path": ""})
58
+ paligemma_config: dict = field(
59
+ default_factory=lambda: {
60
+ "width": 2048,
61
+ "depth": 18,
62
+ "mlp_dim": 16384,
63
+ "num_heads": 8,
64
+ "num_kv_heads": 1,
65
+ "head_dim": 256,
66
+ }
67
+ )
68
+ action_expert_config: dict = field(
69
+ default_factory=lambda: {
70
+ "width": 1024,
71
+ "depth": 18,
72
+ "mlp_dim": 4096,
73
+ "num_heads": 8,
74
+ "num_kv_heads": 1,
75
+ "head_dim": 256,
76
+ }
77
+ )
78
+
79
+
80
+ def _as_dims(config) -> GemmaDims:
81
+ return GemmaDims(
82
+ width=int(config.width),
83
+ depth=int(config.depth),
84
+ mlp_dim=int(config.mlp_dim),
85
+ num_heads=int(config.num_heads),
86
+ num_kv_heads=int(config.num_kv_heads),
87
+ head_dim=int(config.head_dim),
88
+ )
89
+
90
+
91
+ class LazyPaliGemmaTokenizer:
92
+ def __init__(self, max_len: int, model_path: str = ""):
93
+ self.max_len = int(max_len)
94
+ self.model_path = str(model_path or "")
95
+ self._tokenizer = None
96
+
97
+ def _load(self):
98
+ if self._tokenizer is not None:
99
+ return self._tokenizer
100
+ if not self.model_path:
101
+ raise FileNotFoundError(
102
+ "PI0/PI05 tokenizer.model_path is empty. Set framework.tokenizer.model_path "
103
+ "to a local paligemma_tokenizer.model path for raw-text examples."
104
+ )
105
+ import sentencepiece
106
+
107
+ with open(self.model_path, "rb") as f:
108
+ self._tokenizer = sentencepiece.SentencePieceProcessor(model_proto=f.read())
109
+ return self._tokenizer
110
+
111
+ def tokenize(self, prompt: str, state: np.ndarray | torch.Tensor | None = None):
112
+ tokenizer = self._load()
113
+ cleaned_text = str(prompt).strip().replace("_", " ").replace("\n", " ")
114
+ if state is not None:
115
+ state_np = np.asarray(state, dtype=np.float32).reshape(-1)
116
+ discretized_state = np.digitize(state_np, bins=np.linspace(-1, 1, 256 + 1)[:-1]) - 1
117
+ state_str = " ".join(map(str, discretized_state.tolist()))
118
+ prompt_text = f"Task: {cleaned_text}, State: {state_str};\nAction: "
119
+ tokens = tokenizer.encode(prompt_text, add_bos=True)
120
+ else:
121
+ tokens = tokenizer.encode(cleaned_text, add_bos=True) + tokenizer.encode("\n")
122
+
123
+ token_len = len(tokens)
124
+ if token_len < self.max_len:
125
+ padding = [0] * (self.max_len - token_len)
126
+ mask = [True] * token_len + [False] * len(padding)
127
+ tokens = tokens + padding
128
+ else:
129
+ if token_len > self.max_len:
130
+ logger.warning("PI tokenizer length %s exceeds max_token_len=%s; truncating", token_len, self.max_len)
131
+ tokens = tokens[: self.max_len]
132
+ mask = [True] * self.max_len
133
+ return np.asarray(tokens, dtype=np.int64), np.asarray(mask, dtype=bool)
134
+
135
+
136
+ def _gated_residual(x: torch.Tensor, y: torch.Tensor, gate: torch.Tensor | None):
137
+ return x + y if gate is None else x + y * gate
138
+
139
+
140
+ def _norm(layernorm, hidden_states: torch.Tensor, cond: torch.Tensor | None):
141
+ try:
142
+ out = layernorm(hidden_states, cond=cond)
143
+ except TypeError as exc:
144
+ if cond is not None:
145
+ dense = getattr(layernorm, "dense", None)
146
+ if dense is None:
147
+ raise RuntimeError(
148
+ "PI05 requires Gemma adaRMS support, but this layernorm has no dense module."
149
+ ) from exc
150
+ dtype = hidden_states.dtype
151
+ eps = getattr(layernorm, "variance_epsilon", getattr(layernorm, "eps", 1e-6))
152
+ variance = hidden_states.to(torch.float32).pow(2).mean(-1, keepdim=True)
153
+ out = hidden_states * torch.rsqrt(variance + eps)
154
+ scale, shift, gate = dense(cond.to(dense.weight.dtype))[:, None, :].chunk(3, dim=-1)
155
+ return (out * (1 + scale) + shift).to(dtype), gate.to(dtype)
156
+ out = layernorm(hidden_states)
157
+ if isinstance(out, tuple):
158
+ return out
159
+ return out, None
160
+
161
+
162
+ @FRAMEWORK_REGISTRY.register("PI0")
163
+ @FRAMEWORK_REGISTRY.register("Pi0")
164
+ class PI0(baseframework):
165
+ default_config_cls = PI0DefaultConfig
166
+ action_head_cls: type[OpenPIActionHeadBase] = OpenPI0ActionHead
167
+ model_name = "PI0"
168
+ discrete_state_input = False
169
+
170
+ def __init__(self, config=None, **kwargs):
171
+ super().__init__()
172
+ self._init_runtime_config(config)
173
+ self._build_tokenizer()
174
+ self._build_model_modules()
175
+ self.to_precision(self.config.framework.precision)
176
+ torch.set_float32_matmul_precision("highest")
177
+
178
+ def _init_runtime_config(self, config):
179
+ self.config = merge_framework_config(self.default_config_cls, config)
180
+ self.config.framework.name = self.model_name
181
+ self.discrete_state_input = bool(self.config.framework.discrete_state_input)
182
+
183
+ self.action_dim = int(self.config.framework.action_dim)
184
+ self.output_action_dim = int(self.config.framework.get("output_action_dim", self.action_dim))
185
+ if not 0 < self.output_action_dim <= self.action_dim:
186
+ raise ValueError("output_action_dim must be between 1 and native action_dim")
187
+ self.action_horizon = int(self.config.framework.action_horizon)
188
+ self.max_state_dim = int(self.config.framework.max_state_dim)
189
+ self.num_inference_steps = int(self.config.framework.num_inference_steps)
190
+ self.image_resolution = tuple(int(x) for x in self.config.framework.image_resolution)
191
+ self.image_keys = tuple(self.config.framework.image_keys)
192
+ self.state_pad_value = float(self.config.framework.tokenizer.pad_state_value)
193
+
194
+ def _build_tokenizer(self):
195
+ self.tokenizer = LazyPaliGemmaTokenizer(
196
+ max_len=int(self.config.framework.max_token_len),
197
+ model_path=str(self.config.framework.tokenizer.model_path),
198
+ )
199
+
200
+ def _build_model_modules(self):
201
+ vlm_config = _as_dims(self.config.framework.paligemma_config)
202
+ expert_config = _as_dims(self.config.framework.action_expert_config)
203
+ self.vlm = OpenPIPaliGemma(
204
+ vlm_config,
205
+ use_adarms=False,
206
+ model_name_or_path=str(self.config.framework.paligemma.model_name_or_path),
207
+ )
208
+ self.action_head = self.action_head_cls(
209
+ expert_config,
210
+ action_dim=self.action_dim,
211
+ action_horizon=self.action_horizon,
212
+ max_state_dim=self.max_state_dim,
213
+ )
214
+
215
+ def to_precision(self, precision: Literal["bfloat16", "float32"]):
216
+ if precision == "float32":
217
+ self.to(dtype=torch.float32)
218
+ return
219
+ if precision != "bfloat16":
220
+ raise ValueError(f"Unsupported PI precision: {precision}")
221
+ self.to(dtype=torch.bfloat16)
222
+ keep_fp32 = (
223
+ "vision_tower.vision_model.embeddings.patch_embedding.weight",
224
+ "vision_tower.vision_model.embeddings.patch_embedding.bias",
225
+ "vision_tower.vision_model.embeddings.position_embedding.weight",
226
+ "input_layernorm",
227
+ "post_attention_layernorm",
228
+ "model.norm",
229
+ )
230
+ for name, param in self.named_parameters():
231
+ if any(selector in name for selector in keep_fp32):
232
+ param.data = param.data.to(dtype=torch.float32)
233
+
234
+ def _pad_array_2d(self, arr: np.ndarray, target_time: int, target_dim: int, pad_value: float) -> np.ndarray:
235
+ arr = np.asarray(arr, dtype=np.float32)
236
+ if arr.ndim == 1:
237
+ arr = arr[None, :]
238
+ if arr.shape[0] > target_time or arr.shape[1] > target_dim:
239
+ raise ValueError(f"Input shape {arr.shape} exceeds target {(target_time, target_dim)}")
240
+ out = np.full((target_time, target_dim), pad_value, dtype=np.float32)
241
+ out[: arr.shape[0], : arr.shape[1]] = arr
242
+ return out
243
+
244
+ def _pil_to_tensor(self, image) -> torch.Tensor:
245
+ image_np = np.asarray(image.convert("RGB")).copy()
246
+ image_np = resize_with_pad(image_np[None], self.image_resolution[0], self.image_resolution[1])[0]
247
+ return torch.from_numpy(image_np).permute(2, 0, 1).contiguous().float() / 255.0 * 2.0 - 1.0
248
+
249
+ def _prepare_examples(self, examples: list[dict], include_actions: bool):
250
+ images, masks, states, actions, prompts = [], [], [], [], []
251
+ for example in examples:
252
+ raw_images = to_pil_preserve(example["image"])
253
+ if isinstance(raw_images, tuple):
254
+ raw_images = list(raw_images)
255
+ elif not isinstance(raw_images, list):
256
+ raw_images = [raw_images]
257
+ sample_images, sample_masks = [], []
258
+ provided_mask = example.get("image_mask")
259
+ provided_mask = list(provided_mask) if provided_mask is not None else None
260
+ for idx, _key in enumerate(self.image_keys):
261
+ has_image = idx < len(raw_images)
262
+ if has_image:
263
+ image = raw_images[idx]
264
+ else:
265
+ image = raw_images[0].copy()
266
+ image.paste(0, (0, 0, image.size[0], image.size[1]))
267
+ active = bool(provided_mask[idx]) if provided_mask is not None and idx < len(provided_mask) else has_image
268
+ sample_images.append(self._pil_to_tensor(image))
269
+ sample_masks.append(active)
270
+ images.append(torch.stack(sample_images))
271
+ masks.append(sample_masks)
272
+ prompts.append(str(example.get("lang", example.get("language", ""))))
273
+
274
+ state_value = example.get("state")
275
+ if state_value is None:
276
+ fill = self.state_pad_value if self.config.framework.discrete_state_input else 0.0
277
+ state_np = np.full((1, self.max_state_dim), fill, dtype=np.float32)
278
+ else:
279
+ state_np = np.asarray(state_value, dtype=np.float32)
280
+ if state_np.ndim == 2 and state_np.shape[0] > 1:
281
+ state_np = state_np[-1:]
282
+ state_np = self._pad_array_2d(
283
+ state_np,
284
+ 1 if state_np.ndim == 1 else state_np.shape[0],
285
+ self.max_state_dim,
286
+ self.state_pad_value if self.config.framework.discrete_state_input else 0.0,
287
+ )
288
+ states.append(torch.from_numpy(state_np))
289
+ if include_actions:
290
+ action_np = self._pad_array_2d(np.asarray(example["action"], dtype=np.float32), self.action_horizon, self.action_dim, 0.0)
291
+ actions.append(torch.from_numpy(action_np))
292
+
293
+ batch = {
294
+ "image": torch.stack(images),
295
+ "image_mask": torch.as_tensor(np.asarray(masks), dtype=torch.bool),
296
+ "state": torch.stack(states),
297
+ "lang": prompts,
298
+ }
299
+ if include_actions:
300
+ batch["action"] = torch.stack(actions)
301
+ return batch
302
+
303
+ def _build_observation_from_batch(self, batch):
304
+ device = self.action_head.action_in_proj.weight.device
305
+ images = {key: batch["image"][:, idx].to(device=device, dtype=torch.float32) for idx, key in enumerate(self.image_keys)}
306
+ masks = {key: batch["image_mask"][:, idx].to(device=device, dtype=torch.bool) for idx, key in enumerate(self.image_keys)}
307
+ tokens, token_masks = [], []
308
+ for idx, prompt in enumerate(batch["lang"]):
309
+ token_state = batch["state"][idx].squeeze(0) if self.config.framework.discrete_state_input else None
310
+ token, mask = self.tokenizer.tokenize(prompt, token_state)
311
+ tokens.append(token)
312
+ token_masks.append(mask)
313
+ return Observation(
314
+ images=images,
315
+ image_masks=masks,
316
+ tokenized_prompt=torch.as_tensor(np.asarray(tokens), device=device),
317
+ tokenized_prompt_mask=torch.as_tensor(np.asarray(token_masks), device=device),
318
+ state=batch["state"].to(device=device, dtype=torch.float32),
319
+ )
320
+
321
+ def _preprocess_observation(self, observation: Observation):
322
+ return (
323
+ [observation.images[key] for key in self.image_keys],
324
+ [observation.image_masks[key] for key in self.image_keys],
325
+ observation.tokenized_prompt,
326
+ observation.tokenized_prompt_mask,
327
+ observation.state,
328
+ )
329
+
330
+ def embed_prefix(self, images, img_masks, lang_tokens, lang_masks):
331
+ embs, pad_masks, att_masks = [], [], []
332
+ for image, image_mask in zip(images, img_masks, strict=True):
333
+ image_emb = self.vlm.embed_image(image)
334
+ bsize, num_img_embs = image_emb.shape[:2]
335
+ embs.append(image_emb)
336
+ pad_masks.append(image_mask[:, None].expand(bsize, num_img_embs))
337
+ att_masks += [0] * num_img_embs
338
+ lang_emb = self.vlm.embed_language_tokens(lang_tokens)
339
+ embs.append(lang_emb * math.sqrt(lang_emb.shape[-1]))
340
+ pad_masks.append(lang_masks)
341
+ att_masks += [0] * lang_emb.shape[1]
342
+ embs = torch.cat(embs, dim=1)
343
+ pad_masks = torch.cat(pad_masks, dim=1)
344
+ att_masks = torch.tensor(att_masks, dtype=torch.bool, device=pad_masks.device)[None, :]
345
+ return embs, pad_masks, att_masks.expand(pad_masks.shape[0], -1)
346
+
347
+ def forward_with_action_expert(
348
+ self,
349
+ *,
350
+ prefix_embs: torch.Tensor,
351
+ suffix_embs: torch.Tensor,
352
+ attention_mask: torch.Tensor,
353
+ position_ids: torch.LongTensor,
354
+ adarms_cond: torch.Tensor | None,
355
+ ):
356
+ streams = [prefix_embs, suffix_embs]
357
+ conds = [None, adarms_cond]
358
+ models = [self.vlm.language_model, self.action_head.model]
359
+ for layer_idx in range(self.vlm.config.text_config.num_hidden_layers):
360
+ streams = self.forward_shared_gemma_layer(layer_idx, models, streams, conds, attention_mask, position_ids)
361
+ suffix_out, _ = _norm(models[1].norm, streams[1], conds[1])
362
+ return suffix_out
363
+
364
+ def forward_shared_gemma_layer(self, layer_idx, models, streams, conds, attention_mask, position_ids):
365
+ query_states, key_states, value_states, gates = [], [], [], []
366
+ for idx, hidden_states in enumerate(streams):
367
+ layer = models[idx].layers[layer_idx]
368
+ hidden_states, gate = _norm(layer.input_layernorm, hidden_states, conds[idx])
369
+ gates.append(gate)
370
+ input_shape = hidden_states.shape[:-1]
371
+ hidden_shape = (*input_shape, -1, layer.self_attn.head_dim)
372
+ query_states.append(layer.self_attn.q_proj(hidden_states).view(hidden_shape).transpose(1, 2))
373
+ key_states.append(layer.self_attn.k_proj(hidden_states).view(hidden_shape).transpose(1, 2))
374
+ value_states.append(layer.self_attn.v_proj(hidden_states).view(hidden_shape).transpose(1, 2))
375
+
376
+ query_states = torch.cat(query_states, dim=2)
377
+ key_states = torch.cat(key_states, dim=2)
378
+ value_states = torch.cat(value_states, dim=2)
379
+ dummy = torch.zeros(
380
+ query_states.shape[0],
381
+ query_states.shape[2],
382
+ query_states.shape[-1],
383
+ device=query_states.device,
384
+ dtype=query_states.dtype,
385
+ )
386
+ cos, sin = self.vlm.language_model.rotary_emb(dummy, position_ids)
387
+ query_states, key_states = modeling_gemma.apply_rotary_pos_emb(
388
+ query_states, key_states, cos, sin, unsqueeze_dim=1
389
+ )
390
+ att_output, _ = modeling_gemma.eager_attention_forward(
391
+ self.vlm.language_model.layers[layer_idx].self_attn,
392
+ query_states,
393
+ key_states,
394
+ value_states,
395
+ attention_mask,
396
+ self.vlm.language_model.layers[layer_idx].self_attn.scaling,
397
+ )
398
+ head_dim = self.vlm.language_model.layers[layer_idx].self_attn.head_dim
399
+ num_heads = self.vlm.language_model.config.num_attention_heads
400
+ att_output = att_output.reshape(query_states.shape[0], -1, num_heads * head_dim)
401
+
402
+ next_streams = []
403
+ start = 0
404
+ for idx, hidden_states in enumerate(streams):
405
+ layer = models[idx].layers[layer_idx]
406
+ end = start + hidden_states.shape[1]
407
+ layer_att = att_output[:, start:end]
408
+ if layer_att.dtype != layer.self_attn.o_proj.weight.dtype:
409
+ layer_att = layer_att.to(layer.self_attn.o_proj.weight.dtype)
410
+ out = layer.self_attn.o_proj(layer_att)
411
+ out = _gated_residual(hidden_states, out, gates[idx])
412
+ residual = out.clone()
413
+ out, gate = _norm(layer.post_attention_layernorm, out, conds[idx])
414
+ if out.dtype != layer.mlp.up_proj.weight.dtype:
415
+ out = out.to(layer.mlp.up_proj.weight.dtype)
416
+ out = layer.mlp(out)
417
+ next_streams.append(_gated_residual(residual, out, gate))
418
+ start = end
419
+ return next_streams
420
+
421
+ def forward_from_observation(self, observation: Observation, actions: torch.Tensor, noise=None, time=None, return_debug=False, return_per_sample_loss=False, prefix_conditioning=None):
422
+ images, img_masks, lang_tokens, lang_masks, state = self._preprocess_observation(observation)
423
+ if prefix_conditioning is None:
424
+ prefix_conditioning = self.embed_prefix(images, img_masks, lang_tokens, lang_masks)
425
+ prefix_embs, prefix_pad_masks, prefix_att_masks = prefix_conditioning
426
+ x_t, u_t, noise, time = self.action_head.prepare_flow_targets(actions, noise=noise, time=time)
427
+ suffix_embs, suffix_pad_masks, suffix_att_masks, adarms_cond = self.action_head.embed_action_suffix(state.squeeze(1), x_t, time)
428
+ if self.vlm.language_model.layers[0].self_attn.q_proj.weight.dtype == torch.bfloat16:
429
+ prefix_embs = prefix_embs.to(torch.bfloat16)
430
+ suffix_embs = suffix_embs.to(torch.bfloat16)
431
+ pad_masks = torch.cat([prefix_pad_masks, suffix_pad_masks], dim=1)
432
+ att_masks = torch.cat([prefix_att_masks, suffix_att_masks], dim=1)
433
+ attention_mask = make_att_4d_masks(make_att_2d_masks(pad_masks, att_masks))
434
+ position_ids = torch.cumsum(pad_masks, dim=1) - 1
435
+ suffix_out = self.forward_with_action_expert(
436
+ prefix_embs=prefix_embs,
437
+ suffix_embs=suffix_embs,
438
+ attention_mask=attention_mask,
439
+ position_ids=position_ids,
440
+ adarms_cond=adarms_cond,
441
+ )
442
+ v_t = self.action_head.decode_action_velocity(suffix_out)
443
+ per_sample_loss = F.mse_loss(u_t, v_t, reduction="none").mean(dim=(1, 2))
444
+ out = {"v_t": v_t, "action_loss": per_sample_loss if return_per_sample_loss else per_sample_loss.mean()}
445
+ if return_debug:
446
+ out.update({"u_t": u_t, "x_t": x_t, "time": time, "noise": noise})
447
+ return out
448
+
449
+ def forward(self, examples: list[dict] = None, **kwargs):
450
+ '''
451
+ Training forward for PI0 flow-matching action prediction.
452
+ '''
453
+ batch = self._prepare_examples(examples, include_actions=True)
454
+ observation = self._build_observation_from_batch(batch)
455
+ actions = batch["action"].to(device=self.action_head.action_in_proj.weight.device, dtype=torch.float32)
456
+ noise = kwargs.get("noise")
457
+ time = kwargs.get("time")
458
+ seed = kwargs.get("flow_noise_seed")
459
+ if seed is not None and noise is None and time is None:
460
+ devices = [torch.cuda.current_device()] if torch.cuda.is_available() else []
461
+ with torch.random.fork_rng(devices=devices):
462
+ torch.manual_seed(int(seed))
463
+ noise = self.action_head.sample_action_noise(actions.shape, actions.device)
464
+ time = self.action_head.sample_flow_time(actions.shape[0], actions.device)
465
+ return self.forward_from_observation(
466
+ observation,
467
+ actions,
468
+ noise=noise,
469
+ time=time,
470
+ return_debug=bool(kwargs.get("return_debug", False)),
471
+ return_per_sample_loss=bool(kwargs.get("return_per_sample_loss", False)),
472
+ )
473
+
474
+ @torch.inference_mode()
475
+ def predict_action(self, examples: list[dict] | dict = None, **kwargs):
476
+ '''
477
+ Prediction forward for PI0 flow-matching action prediction
478
+ '''
479
+ if not isinstance(examples, list):
480
+ examples = [examples]
481
+ batch = self._prepare_examples(examples, include_actions=False)
482
+ observation = self._build_observation_from_batch(batch)
483
+ actions = self.sample_actions(
484
+ observation,
485
+ noise=kwargs.get("noise"),
486
+ num_steps=int(kwargs.get("num_steps", kwargs.get("num_inference_steps", self.num_inference_steps))),
487
+ )
488
+ return {"normalized_actions": actions[..., :self.output_action_dim].float().cpu().numpy()}
489
+
490
+ def sample_actions(self, observation: Observation, noise=None, num_steps: int = 10):
491
+ device = self.action_head.action_in_proj.weight.device
492
+ images, img_masks, lang_tokens, lang_masks, state = self._preprocess_observation(observation)
493
+ prefix_embs, prefix_pad_masks, prefix_att_masks = self.embed_prefix(images, img_masks, lang_tokens, lang_masks)
494
+ prefix_att_2d_masks_4d = make_att_4d_masks(make_att_2d_masks(prefix_pad_masks, prefix_att_masks))
495
+ prefix_position_ids = torch.cumsum(prefix_pad_masks, dim=1) - 1
496
+ self.vlm.language_model.config._attn_implementation = "eager"
497
+ prefix_output = self.vlm.forward_prefix(
498
+ inputs_embeds=prefix_embs,
499
+ attention_mask=prefix_att_2d_masks_4d,
500
+ position_ids=prefix_position_ids,
501
+ past_key_values=None,
502
+ use_cache=True,
503
+ )
504
+ past_key_values = prefix_output.past_key_values
505
+ bsize = state.shape[0]
506
+ shape = (bsize, self.action_horizon, self.action_dim)
507
+ x_t = self.action_head.sample_action_noise(shape, device) if noise is None else torch.as_tensor(noise, device=device, dtype=torch.float32)
508
+ if x_t.ndim == 2:
509
+ x_t = x_t[None]
510
+ dt = torch.tensor(-1.0 / max(num_steps, 1), dtype=torch.float32, device=device)
511
+ time = torch.tensor(1.0, dtype=torch.float32, device=device)
512
+ while time >= -dt / 2:
513
+ v_t = self.denoise_step(state.squeeze(1), prefix_pad_masks, past_key_values, x_t, time.expand(bsize))
514
+ x_t = x_t + dt * v_t
515
+ time += dt
516
+ return x_t
517
+
518
+ def denoise_step(self, state, prefix_pad_masks, past_key_values, x_t, timestep):
519
+ suffix_embs, suffix_pad_masks, suffix_att_masks, _adarms_cond = self.action_head.embed_action_suffix(state, x_t, timestep)
520
+ suffix_len = suffix_pad_masks.shape[1]
521
+ bsize, prefix_len = prefix_pad_masks.shape
522
+ prefix_pad_2d_masks = prefix_pad_masks[:, None, :].expand(bsize, suffix_len, prefix_len)
523
+ suffix_att_2d_masks = make_att_2d_masks(suffix_pad_masks, suffix_att_masks)
524
+ attention_mask = make_att_4d_masks(torch.cat([prefix_pad_2d_masks, suffix_att_2d_masks], dim=2))
525
+ position_ids = torch.sum(prefix_pad_masks, dim=-1)[:, None] + torch.cumsum(suffix_pad_masks, dim=1) - 1
526
+ suffix_embs = suffix_embs.to(torch.bfloat16) if self.vlm.language_model.layers[0].self_attn.q_proj.weight.dtype == torch.bfloat16 else suffix_embs
527
+ return self.action_head(
528
+ attention_mask=attention_mask,
529
+ position_ids=position_ids,
530
+ past_key_values=past_key_values,
531
+ suffix_embs=suffix_embs,
532
+ adarms_cond=_adarms_cond,
533
+ )
534
+
535
+ def _is_expected_openpi_missing_key(self, key: str) -> bool:
536
+ if not getattr(self.action_head, "use_adarms", False):
537
+ return False
538
+ prefix = "action_head.gemma_expert.model."
539
+ if key == prefix + "norm.weight":
540
+ return True
541
+ if not key.startswith(prefix + "layers."):
542
+ return False
543
+ return key.endswith(".input_layernorm.weight") or key.endswith(".post_attention_layernorm.weight")
544
+
545
+ @staticmethod
546
+ def _resolve_checkpoint_path(checkpoint_path: str | Path) -> Path:
547
+ path = Path(checkpoint_path)
548
+ if path.is_dir():
549
+ for name in ("model.safetensors", "pytorch_model.bin", "model.pt", "checkpoint.pt", "ckpt.pt"):
550
+ candidate = path / name
551
+ if candidate.exists():
552
+ return candidate
553
+ raise FileNotFoundError(f"No supported PI checkpoint file found under {path}")
554
+ return path
555
+
556
+ @classmethod
557
+ def _load_checkpoint_state_dict(cls, checkpoint_path: str | Path):
558
+ checkpoint_path = cls._resolve_checkpoint_path(checkpoint_path)
559
+ if str(checkpoint_path).endswith(".safetensors"):
560
+ from safetensors.torch import load_file
561
+
562
+ state_dict = load_file(str(checkpoint_path))
563
+ else:
564
+ state_dict = torch.load(checkpoint_path, map_location="cpu")
565
+ if isinstance(state_dict, dict):
566
+ for key in ("state_dict", "model_state_dict", "module", "model"):
567
+ if key in state_dict and isinstance(state_dict[key], dict):
568
+ state_dict = state_dict[key]
569
+ break
570
+ return state_dict
571
+
572
+ def _normalize_state_dict(self, state_dict: dict):
573
+ current = self.state_dict()
574
+ normalized = {}
575
+ for key, value in state_dict.items():
576
+ clean_key = key
577
+ changed = True
578
+ while changed:
579
+ changed = False
580
+ for prefix in ("model.", "module.", "_orig_mod."):
581
+ if clean_key.startswith(prefix):
582
+ clean_key = clean_key[len(prefix):]
583
+ changed = True
584
+ if clean_key.startswith("paligemma_with_expert.paligemma.model."):
585
+ clean_key = clean_key.replace(
586
+ "paligemma_with_expert.paligemma.model.",
587
+ "vlm.model.model.",
588
+ 1,
589
+ )
590
+ elif clean_key.startswith("paligemma_with_expert.paligemma."):
591
+ clean_key = clean_key.replace("paligemma_with_expert.paligemma.", "vlm.model.", 1)
592
+ elif clean_key.startswith("paligemma_with_expert.gemma_expert."):
593
+ clean_key = clean_key.replace("paligemma_with_expert.gemma_expert.", "action_head.gemma_expert.", 1)
594
+ elif clean_key.startswith("action_in_proj."):
595
+ clean_key = clean_key.replace("action_in_proj.", "action_head.action_in_proj.", 1)
596
+ elif clean_key.startswith("action_out_proj."):
597
+ clean_key = clean_key.replace("action_out_proj.", "action_head.action_out_proj.", 1)
598
+ elif clean_key.startswith("state_proj."):
599
+ clean_key = clean_key.replace("state_proj.", "action_head.state_proj.", 1)
600
+ elif clean_key.startswith("action_time_mlp_in."):
601
+ clean_key = clean_key.replace("action_time_mlp_in.", "action_head.action_time_mlp_in.", 1)
602
+ elif clean_key.startswith("action_time_mlp_out."):
603
+ clean_key = clean_key.replace("action_time_mlp_out.", "action_head.action_time_mlp_out.", 1)
604
+ elif clean_key.startswith("time_mlp_in."):
605
+ clean_key = clean_key.replace("time_mlp_in.", "action_head.time_mlp_in.", 1)
606
+ elif clean_key.startswith("time_mlp_out."):
607
+ clean_key = clean_key.replace("time_mlp_out.", "action_head.time_mlp_out.", 1)
608
+ normalized[clean_key] = value
609
+ embed_key = "vlm.model.model.language_model.embed_tokens.weight"
610
+ lm_head_key = "vlm.model.lm_head.weight"
611
+ if embed_key not in normalized and lm_head_key in normalized and embed_key in current:
612
+ if normalized[lm_head_key].shape == current[embed_key].shape:
613
+ normalized[embed_key] = normalized[lm_head_key]
614
+ return normalized
615
+
616
+ def load_pretrained_checkpoint(self, checkpoint_path: str | Path, reload_modules: str | None = None):
617
+ state_dict = self._normalize_state_dict(self._load_checkpoint_state_dict(checkpoint_path))
618
+ if reload_modules:
619
+ for path in [p.strip() for p in reload_modules.split(",") if p.strip()]:
620
+ module = self
621
+ for part in path.split("."):
622
+ module = getattr(module, part)
623
+ prefix = path + "."
624
+ sub_state = {k[len(prefix):]: v for k, v in state_dict.items() if k.startswith(prefix)}
625
+ module.load_state_dict(sub_state, strict=False)
626
+ return self
627
+
628
+ current = self.state_dict()
629
+ filtered, shape_mismatch = {}, []
630
+ for key, value in state_dict.items():
631
+ if key in current and getattr(value, "shape", None) == current[key].shape:
632
+ filtered[key] = value
633
+ elif key in current:
634
+ shape_mismatch.append((key, tuple(value.shape), tuple(current[key].shape)))
635
+ missing, unexpected = self.load_state_dict(filtered, strict=False)
636
+ expected_missing = [key for key in missing if self._is_expected_openpi_missing_key(key)]
637
+ real_missing = [key for key in missing if key not in expected_missing]
638
+ logger.info(
639
+ "%s checkpoint load: checkpoint=%s loaded=%s model=%s missing=%s expected_missing=%s unexpected=%s shape_mismatch=%s",
640
+ type(self).__name__,
641
+ len(state_dict),
642
+ len(filtered),
643
+ len(current),
644
+ len(real_missing),
645
+ len(expected_missing),
646
+ len(unexpected),
647
+ len(shape_mismatch),
648
+ )
649
+ if real_missing:
650
+ logger.info("%s first missing keys: %s", type(self).__name__, real_missing[:10])
651
+ if shape_mismatch:
652
+ logger.info("%s first shape mismatches: %s", type(self).__name__, shape_mismatch[:10])
653
+ return self
starVLA/model/framework/VLM4A/PI05.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Literal
5
+
6
+ from starVLA.model.framework.VLM4A.PI0 import PI0, PI0DefaultConfig, IMAGE_KEYS
7
+ from starVLA.model.modules.action_model.OpenPI_ActionHead import OpenPI05ActionHead
8
+ from starVLA.model.tools import FRAMEWORK_REGISTRY
9
+
10
+
11
+ @dataclass
12
+ class PI05DefaultConfig(PI0DefaultConfig):
13
+ name: str = "PI05"
14
+ precision: Literal["bfloat16", "float32"] = "bfloat16"
15
+
16
+ action_dim: int = 32
17
+ action_horizon: int = 50
18
+ max_state_dim: int = 32
19
+ discrete_state_input: bool = True
20
+ max_token_len: int = 200
21
+ num_inference_steps: int = 10
22
+
23
+ image_resolution: list[int] = field(default_factory=lambda: [224, 224])
24
+ image_keys: list[str] = field(default_factory=lambda: list(IMAGE_KEYS))
25
+
26
+ tokenizer: dict = field(default_factory=lambda: {"model_path": "", "pad_state_value": -2.0})
27
+ paligemma: dict = field(default_factory=lambda: {"model_name_or_path": ""})
28
+ paligemma_config: dict = field(
29
+ default_factory=lambda: {
30
+ "width": 2048,
31
+ "depth": 18,
32
+ "mlp_dim": 16384,
33
+ "num_heads": 8,
34
+ "num_kv_heads": 1,
35
+ "head_dim": 256,
36
+ }
37
+ )
38
+ action_expert_config: dict = field(
39
+ default_factory=lambda: {
40
+ "width": 1024,
41
+ "depth": 18,
42
+ "mlp_dim": 4096,
43
+ "num_heads": 8,
44
+ "num_kv_heads": 1,
45
+ "head_dim": 256,
46
+ }
47
+ )
48
+
49
+
50
+ @FRAMEWORK_REGISTRY.register("PI05")
51
+ @FRAMEWORK_REGISTRY.register("Pi05")
52
+ class PI05(PI0):
53
+ """OpenPI PI05 model exposed through the StarVLA framework API."""
54
+
55
+ default_config_cls = PI05DefaultConfig
56
+ action_head_cls = OpenPI05ActionHead
57
+ model_name = "PI05"
58
+ discrete_state_input = True
starVLA/model/framework/VLM4A/QwenFast.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Qwen2.5-VL + FAST autoregressive action policy for ReTime.
2
+
3
+ The action tokens and checkpoint follow StarVLA's QwenFast implementation.
4
+ The per-example action-token NLL is exposed for temporal importance sampling.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import numpy as np
10
+ import torch
11
+ import torch.nn as nn
12
+ import torch.nn.functional as F
13
+ from PIL import Image
14
+ from qwen_vl_utils import process_vision_info
15
+ from transformers import AutoProcessor, Qwen2_5_VLForConditionalGeneration
16
+
17
+ from starVLA.model.framework.base_framework import baseframework
18
+ from starVLA.model.tools import FRAMEWORK_REGISTRY
19
+
20
+
21
+ class _Qwen25Interface(nn.Module):
22
+ def __init__(self, model_path: str, *, attn_implementation: str = "sdpa"):
23
+ super().__init__()
24
+ self.model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
25
+ model_path, attn_implementation=attn_implementation, dtype=torch.bfloat16
26
+ )
27
+ self.processor = AutoProcessor.from_pretrained(model_path)
28
+ self.processor.tokenizer.padding_side = "left"
29
+ self.action_token_min = self.processor.tokenizer.convert_tokens_to_ids("<robot_action_0>")
30
+ self.action_token_max = self.processor.tokenizer.convert_tokens_to_ids("<robot_action_2047>")
31
+ if (
32
+ self.action_token_min < 0
33
+ or self.action_token_max - self.action_token_min != 2047
34
+ or self.action_token_max >= self.model.config.text_config.vocab_size
35
+ ):
36
+ raise ValueError("QwenFast requires 2048 contiguous <robot_action_i> tokens")
37
+
38
+
39
+ class _FastActionEncoder(nn.Module):
40
+ def __init__(self, path: str, horizon: int, action_dim: int):
41
+ super().__init__()
42
+ self.fast_tokenizer = AutoProcessor.from_pretrained(path, trust_remote_code=True)
43
+ self.fast_tokenizer.time_horizon = horizon
44
+ self.fast_tokenizer.action_dim = action_dim
45
+
46
+ def encode(self, actions: np.ndarray) -> list[list[int]]:
47
+ tokens = self.fast_tokenizer(actions)
48
+ if any(not seq or min(seq) < 0 or max(seq) >= 2048 for seq in tokens):
49
+ raise ValueError("FAST produced an empty or out-of-range action sequence")
50
+ return tokens
51
+
52
+
53
+ @FRAMEWORK_REGISTRY.register("QwenFast")
54
+ class QwenFast(baseframework):
55
+ """Autoregressive action model compatible with StarVLA's 10K Bridge state."""
56
+
57
+ def __init__(self, config):
58
+ super().__init__()
59
+ self.config = config
60
+ framework = config.framework
61
+ action = framework.action_model
62
+ self.action_horizon = int(action.action_horizon)
63
+ self.action_dim = int(action.action_dim)
64
+ self.qwen_vl_interface = _Qwen25Interface(
65
+ str(framework.qwenvl.base_vlm),
66
+ attn_implementation=str(framework.qwenvl.get("attn_implementation", "sdpa")),
67
+ )
68
+ self.action_model = _FastActionEncoder(
69
+ str(action.fast_tokenizer_path), self.action_horizon, self.action_dim
70
+ )
71
+ self.cot_prompt = config.datasets.vla_data.get("CoT_prompt", None)
72
+
73
+ def _inputs(self, examples: list[dict], *, with_actions: bool):
74
+ if not examples:
75
+ raise ValueError("examples must be non-empty")
76
+ actions = None
77
+ if with_actions:
78
+ actions = np.stack(
79
+ [np.asarray(e["action"], dtype=np.float32)[-self.action_horizon :] for e in examples]
80
+ )
81
+ if actions.shape[1:] != (self.action_horizon, self.action_dim):
82
+ raise ValueError("actions must have the configured horizon and dimension")
83
+ tokens = self.action_model.encode(actions)
84
+ messages = []
85
+ for i, example in enumerate(examples):
86
+ images = example["image"]
87
+ if isinstance(images, (Image.Image, np.ndarray)):
88
+ images = [images]
89
+ if not images:
90
+ raise ValueError("each example requires at least one image")
91
+ prepared_images = []
92
+ for image in images:
93
+ if isinstance(image, np.ndarray):
94
+ if image.ndim != 3 or image.shape[-1] != 3 or image.dtype != np.uint8:
95
+ raise ValueError("NumPy images must be HWC RGB uint8")
96
+ image = Image.fromarray(image)
97
+ if not isinstance(image, Image.Image):
98
+ raise ValueError("Images must be PIL or HWC RGB uint8 arrays")
99
+ prepared_images.append(image)
100
+ instruction = str(example["lang"])
101
+ if self.cot_prompt:
102
+ instruction = self.cot_prompt.replace("{instruction}", instruction)
103
+ content = [{"type": "image", "image": image} for image in prepared_images]
104
+ content.append({"type": "text", "text": instruction})
105
+ message = [{"role": "user", "content": content}]
106
+ if with_actions:
107
+ solution = "".join(f"<robot_action_{token}>" for token in tokens[i])
108
+ message.append({"role": "assistant", "content": [{"type": "text", "text": solution}]})
109
+ messages.append(message)
110
+
111
+ text = [
112
+ self.qwen_vl_interface.processor.apply_chat_template(
113
+ message, tokenize=False, add_generation_prompt=True
114
+ )
115
+ for message in messages
116
+ ]
117
+ image_inputs, video_inputs = process_vision_info(messages)
118
+ batch = self.qwen_vl_interface.processor(
119
+ text=text, images=image_inputs, videos=video_inputs, padding=True, return_tensors="pt"
120
+ )
121
+ if with_actions:
122
+ ids = batch["input_ids"]
123
+ action_mask = (ids >= self.qwen_vl_interface.action_token_min) & (
124
+ ids <= self.qwen_vl_interface.action_token_max
125
+ )
126
+ expected = torch.tensor([len(seq) for seq in tokens])
127
+ if not torch.equal(action_mask.sum(dim=1).cpu(), expected):
128
+ raise ValueError("Processor changed the FAST action token sequence")
129
+ labels = torch.full_like(ids, -100)
130
+ labels[action_mask] = ids[action_mask]
131
+ batch["labels"] = labels
132
+ return batch.to(next(self.qwen_vl_interface.model.parameters()).device)
133
+
134
+ def forward(self, examples: list[dict], *, return_per_sample_loss: bool = False, flow_noise_seed=None, **kwargs):
135
+ if kwargs:
136
+ raise TypeError(f"Unexpected QwenFast forward arguments: {sorted(kwargs)}")
137
+ batch = self._inputs(examples, with_actions=True)
138
+ labels = batch.pop("labels")
139
+ outputs = self.qwen_vl_interface.model(**batch, use_cache=False, return_dict=True)
140
+ shifted_labels = labels[:, 1:]
141
+ mask = shifted_labels != -100
142
+ if not torch.all(mask.sum(dim=1) > 0):
143
+ raise ValueError("Every sample must contain an action target")
144
+ shifted_logits = outputs.logits[:, :-1]
145
+ token_loss = F.cross_entropy(shifted_logits[mask].float(), shifted_labels[mask], reduction="none")
146
+ sample_index = torch.arange(len(examples), device=mask.device).unsqueeze(1).expand_as(mask)[mask]
147
+ sums = torch.zeros(len(examples), dtype=token_loss.dtype, device=token_loss.device)
148
+ per_sample = sums.index_add(0, sample_index, token_loss) / mask.sum(dim=1)
149
+ return {"action_loss": per_sample if return_per_sample_loss else per_sample.mean()}
150
+
151
+ @torch.inference_mode()
152
+ def predict_action(self, examples: list[dict], **kwargs):
153
+ if isinstance(examples, dict):
154
+ examples = [examples]
155
+ batch = self._inputs(examples, with_actions=False)
156
+ prompt_length = batch["input_ids"].shape[1]
157
+ generated = self.qwen_vl_interface.model.generate(
158
+ **batch, max_new_tokens=kwargs.pop("max_new_tokens", 256), **kwargs
159
+ )[:, prompt_length:]
160
+ low = self.qwen_vl_interface.action_token_min
161
+ high = self.qwen_vl_interface.action_token_max
162
+ tokens = [row[(row >= low) & (row <= high)].sub(low).tolist() for row in generated]
163
+ for sequence in tokens:
164
+ decoded = self.action_model.fast_tokenizer.bpe_tokenizer.decode(sequence)
165
+ if len(decoded) != self.action_horizon * self.action_dim:
166
+ raise ValueError(
167
+ "Generated FAST action sequence is incomplete or malformed: "
168
+ f"decoded {len(decoded)} coefficients, expected "
169
+ f"{self.action_horizon * self.action_dim}"
170
+ )
171
+ actions = self.action_model.fast_tokenizer.decode(
172
+ tokens, time_horizon=self.action_horizon, action_dim=self.action_dim
173
+ )
174
+ return {"normalized_actions": actions}
starVLA/model/framework/VLM4A/QwenGR00T.py CHANGED
@@ -1,7 +1,6 @@
 
1
  # Copyright 2025 starVLA community. All rights reserved.
2
  # Licensed under the MIT License, Version 1.0 (the "License");
3
- # Implemented by [Junqiu YU / Fudan University] in [2025].
4
- # Design and Merged by [Jinhui YE / HKUST University] in [2025].
5
  """
6
  Qwen-GR00T Framework
7
  A lightweight implementation that Qwen-VL + Flow-matching head to directly predict continuous actions
@@ -178,6 +177,7 @@ class Qwen_GR00T(baseframework):
178
  """ """
179
  return_per_sample_loss = bool(kwargs.pop("return_per_sample_loss", False))
180
  flow_noise_seed = kwargs.pop("flow_noise_seed", None)
 
181
  if kwargs:
182
  raise TypeError(f"Unexpected forward arguments: {sorted(kwargs)}")
183
  batch_images = [example["image"] for example in examples] # [B,[PLT]]
@@ -215,11 +215,42 @@ class Qwen_GR00T(baseframework):
215
  state = torch.tensor(np.array(state), device=last_hidden.device, dtype=last_hidden.dtype)
216
  state_repeated = state.repeat(repeated_diffusion_steps, 1, 1)
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  action_loss = self.action_model(
219
  last_hidden_repeated, actions_target_repeated, state_repeated,
220
  reduction="none" if return_per_sample_loss else "mean",
221
  flow_noise_seed=flow_noise_seed,
 
 
 
222
  ) # (B, chunk_len, action_dim)
 
 
 
 
223
  if return_per_sample_loss:
224
  action_loss = aggregate_repeated_losses(
225
  action_loss,
@@ -227,7 +258,11 @@ class Qwen_GR00T(baseframework):
227
  batch_size=len(examples),
228
  )
229
 
230
- return {"action_loss": action_loss}
 
 
 
 
231
 
232
  @torch.inference_mode()
233
  def predict_action(
 
1
+ # Modified in the ReTime research artifact; see NOTICE.
2
  # Copyright 2025 starVLA community. All rights reserved.
3
  # Licensed under the MIT License, Version 1.0 (the "License");
 
 
4
  """
5
  Qwen-GR00T Framework
6
  A lightweight implementation that Qwen-VL + Flow-matching head to directly predict continuous actions
 
177
  """ """
178
  return_per_sample_loss = bool(kwargs.pop("return_per_sample_loss", False))
179
  flow_noise_seed = kwargs.pop("flow_noise_seed", None)
180
+ return_flow_noise_time = bool(kwargs.pop("return_flow_noise_time", False))
181
  if kwargs:
182
  raise TypeError(f"Unexpected forward arguments: {sorted(kwargs)}")
183
  batch_images = [example["image"] for example in examples] # [B,[PLT]]
 
215
  state = torch.tensor(np.array(state), device=last_hidden.device, dtype=last_hidden.dtype)
216
  state_repeated = state.repeat(repeated_diffusion_steps, 1, 1)
217
 
218
+ explicit_flow = [
219
+ "loss_is_flow_noise" in example and "loss_is_flow_time" in example
220
+ for example in examples
221
+ ]
222
+ if any(explicit_flow) and not all(explicit_flow):
223
+ raise ValueError("A batch cannot mix explicit and sampled flow realizations")
224
+ flow_noise = None
225
+ flow_time = None
226
+ if all(explicit_flow):
227
+ flow_noise = torch.stack(
228
+ [
229
+ torch.as_tensor(example["loss_is_flow_noise"])
230
+ for example in examples
231
+ ],
232
+ dim=1,
233
+ ).reshape(actions_target_repeated.shape)
234
+ flow_time = torch.stack(
235
+ [
236
+ torch.as_tensor(example["loss_is_flow_time"])
237
+ for example in examples
238
+ ],
239
+ dim=1,
240
+ ).reshape(actions_target_repeated.shape[0])
241
+
242
  action_loss = self.action_model(
243
  last_hidden_repeated, actions_target_repeated, state_repeated,
244
  reduction="none" if return_per_sample_loss else "mean",
245
  flow_noise_seed=flow_noise_seed,
246
+ flow_noise=flow_noise,
247
+ flow_time=flow_time,
248
+ return_flow_noise_time=return_flow_noise_time,
249
  ) # (B, chunk_len, action_dim)
250
+ sampled_noise = None
251
+ sampled_time = None
252
+ if return_flow_noise_time:
253
+ action_loss, sampled_noise, sampled_time = action_loss
254
  if return_per_sample_loss:
255
  action_loss = aggregate_repeated_losses(
256
  action_loss,
 
258
  batch_size=len(examples),
259
  )
260
 
261
+ output = {"action_loss": action_loss}
262
+ if return_flow_noise_time:
263
+ output["flow_noise"] = sampled_noise
264
+ output["flow_time"] = sampled_time
265
+ return output
266
 
267
  @torch.inference_mode()
268
  def predict_action(
starVLA/model/framework/base_framework.py CHANGED
@@ -1,3 +1,4 @@
 
1
  """
2
  Base framework abstraction providing:
3
  - Pretrained loading (config + normalization stats + weights)
@@ -94,7 +95,7 @@ def build_framework(cfg): # The single entry point for building different model
94
 
95
 
96
  # PreTrainedModel, AutoModel, PretrainedConfig, are so good, find sometime to study them
97
- # TODO @JinhuiYE find sometime to merge yaml config with transformer config
98
 
99
 
100
  class baseframework(PreTrainedModel):
 
1
+ # Modified in the ReTime research artifact; see NOTICE.
2
  """
3
  Base framework abstraction providing:
4
  - Pretrained loading (config + normalization stats + weights)
 
95
 
96
 
97
  # PreTrainedModel, AutoModel, PretrainedConfig, are so good, find sometime to study them
98
+ # Framework construction merges the YAML and transformer configurations.
99
 
100
 
101
  class baseframework(PreTrainedModel):
starVLA/model/framework/share_tools.py CHANGED
@@ -1,3 +1,4 @@
 
1
  """
2
  Shared configuration / utility helpers for framework components:
3
  - NamespaceWithGet: lightweight namespace behaving like a dict
@@ -349,7 +350,7 @@ def read_model_config(pretrained_checkpoint):
349
  with open(dataset_statistics_json, "r") as f:
350
  norm_stats = json.load(f)
351
  else:
352
- overwatch.error(f"❌ Pretrained checkpoint `{pretrained_checkpoint}` does not exist.")
353
  raise FileNotFoundError(f"Pretrained checkpoint `{pretrained_checkpoint}` does not exist.")
354
  return global_cfg, norm_stats
355
 
@@ -387,30 +388,20 @@ def read_mode_config(pretrained_checkpoint):
387
  apply_config_compat(ocfg)
388
  global_cfg = OmegaConf.to_container(ocfg, resolve=True)
389
  except Exception as e:
390
- overwatch.error(f"❌ Failed to load YAML config `{config_yaml}`: {e}")
391
  raise
392
 
393
  # Load Dataset Statistics for Action Denormalization
394
  with open(dataset_statistics_json, "r") as f:
395
  norm_stats = json.load(f)
396
  else:
397
- overwatch.error(f"❌ Pretrained checkpoint `{pretrained_checkpoint}` does not exist.")
398
  raise FileNotFoundError(f"Pretrained checkpoint `{pretrained_checkpoint}` does not exist.")
399
  return global_cfg, norm_stats
400
 
401
 
402
- # =============================================================================
403
- # Config compatibility / "tightening" layer (introduced in version_id "0.21").
404
- #
405
- # Goal: keep user-facing YAMLs short and unambiguous while preserving full
406
- # back-compat for old checkpoints' config.yaml. See bar/config_收紧.md for
407
- # the design rationale.
408
- #
409
- # This function is *idempotent* — calling it multiple times yields the same
410
- # result. It does NOT touch framework class signatures; instead it normalises
411
- # the OmegaConf tree so that downstream framework __init__ code (which still
412
- # reads e.g. `future_action_window_size`) keeps working unchanged.
413
- # =============================================================================
414
 
415
  CONFIG_VERSION = "0.21"
416
 
 
1
+ # Modified in the ReTime research artifact; see NOTICE.
2
  """
3
  Shared configuration / utility helpers for framework components:
4
  - NamespaceWithGet: lightweight namespace behaving like a dict
 
350
  with open(dataset_statistics_json, "r") as f:
351
  norm_stats = json.load(f)
352
  else:
353
+ overwatch.error(f"Pretrained checkpoint `{pretrained_checkpoint}` does not exist.")
354
  raise FileNotFoundError(f"Pretrained checkpoint `{pretrained_checkpoint}` does not exist.")
355
  return global_cfg, norm_stats
356
 
 
388
  apply_config_compat(ocfg)
389
  global_cfg = OmegaConf.to_container(ocfg, resolve=True)
390
  except Exception as e:
391
+ overwatch.error(f"Failed to load YAML config `{config_yaml}`: {e}")
392
  raise
393
 
394
  # Load Dataset Statistics for Action Denormalization
395
  with open(dataset_statistics_json, "r") as f:
396
  norm_stats = json.load(f)
397
  else:
398
+ overwatch.error(f"Pretrained checkpoint `{pretrained_checkpoint}` does not exist.")
399
  raise FileNotFoundError(f"Pretrained checkpoint `{pretrained_checkpoint}` does not exist.")
400
  return global_cfg, norm_stats
401
 
402
 
403
+ # Normalize legacy configuration keys before model construction. The
404
+ # transformation is idempotent and leaves framework class signatures intact.
 
 
 
 
 
 
 
 
 
 
405
 
406
  CONFIG_VERSION = "0.21"
407
 
starVLA/model/modules/action_model/GR00T_ActionHeader.py CHANGED
@@ -1,5 +1,5 @@
 
1
  # Copyright 2025 NVIDIA Corp. and affiliates. All rights reserved.
2
- # Modified by [Junqiu YU/ Fudan University] in [2025].
3
  # Modification: [rm and add some connect adapter to match with starVLA, e.g., "rm "].
4
  # Action repeat is inspired by CogACT
5
 
@@ -311,7 +311,21 @@ class FlowmatchingActionHead(nn.Module):
311
  self,
312
  actions: torch.Tensor,
313
  flow_noise_seed: int | None,
 
 
314
  ) -> tuple[torch.Tensor, torch.Tensor]:
 
 
 
 
 
 
 
 
 
 
 
 
315
  def sample() -> tuple[torch.Tensor, torch.Tensor]:
316
  noise = torch.randn(
317
  actions.shape,
@@ -391,6 +405,9 @@ class FlowmatchingActionHead(nn.Module):
391
  encoder_attention_mask=None,
392
  reduction: str = "mean",
393
  flow_noise_seed: int | None = None,
 
 
 
394
  ):
395
  """
396
  vl_embs: shape (B, seq_length, feature_dim)
@@ -400,6 +417,8 @@ class FlowmatchingActionHead(nn.Module):
400
  noise, t = self._sample_training_noise_and_time(
401
  actions,
402
  flow_noise_seed,
 
 
403
  )
404
  t = t[:, None, None] # shape (B,1,1) for broadcast
405
 
@@ -416,7 +435,10 @@ class FlowmatchingActionHead(nn.Module):
416
 
417
  # Slice out only the action portion of pred and target.
418
  elementwise_loss = (pred_actions - velocity) ** 2
419
- return reduce_flow_matching_loss(elementwise_loss, reduction=reduction)
 
 
 
420
 
421
  @torch.no_grad()
422
  def predict_action(
 
1
+ # Modified in the ReTime research artifact; see NOTICE.
2
  # Copyright 2025 NVIDIA Corp. and affiliates. All rights reserved.
 
3
  # Modification: [rm and add some connect adapter to match with starVLA, e.g., "rm "].
4
  # Action repeat is inspired by CogACT
5
 
 
311
  self,
312
  actions: torch.Tensor,
313
  flow_noise_seed: int | None,
314
+ flow_noise: torch.Tensor | None = None,
315
+ flow_time: torch.Tensor | None = None,
316
  ) -> tuple[torch.Tensor, torch.Tensor]:
317
+ if (flow_noise is None) != (flow_time is None):
318
+ raise ValueError("flow_noise and flow_time must be supplied together")
319
+ if flow_noise is not None:
320
+ if tuple(flow_noise.shape) != tuple(actions.shape):
321
+ raise ValueError("Explicit flow noise does not match the action shape")
322
+ if tuple(flow_time.shape) != (actions.shape[0],):
323
+ raise ValueError("Explicit flow time does not match the batch shape")
324
+ return (
325
+ flow_noise.to(device=actions.device, dtype=actions.dtype),
326
+ flow_time.to(device=actions.device, dtype=actions.dtype),
327
+ )
328
+
329
  def sample() -> tuple[torch.Tensor, torch.Tensor]:
330
  noise = torch.randn(
331
  actions.shape,
 
405
  encoder_attention_mask=None,
406
  reduction: str = "mean",
407
  flow_noise_seed: int | None = None,
408
+ flow_noise: torch.Tensor | None = None,
409
+ flow_time: torch.Tensor | None = None,
410
+ return_flow_noise_time: bool = False,
411
  ):
412
  """
413
  vl_embs: shape (B, seq_length, feature_dim)
 
417
  noise, t = self._sample_training_noise_and_time(
418
  actions,
419
  flow_noise_seed,
420
+ flow_noise=flow_noise,
421
+ flow_time=flow_time,
422
  )
423
  t = t[:, None, None] # shape (B,1,1) for broadcast
424
 
 
435
 
436
  # Slice out only the action portion of pred and target.
437
  elementwise_loss = (pred_actions - velocity) ** 2
438
+ loss = reduce_flow_matching_loss(elementwise_loss, reduction=reduction)
439
+ if return_flow_noise_time:
440
+ return loss, noise, t[:, 0, 0]
441
+ return loss
442
 
443
  @torch.no_grad()
444
  def predict_action(
starVLA/model/modules/action_model/OpenPI_ActionHead.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import inspect
5
+
6
+ import torch
7
+ from torch import Tensor, nn
8
+ import torch.nn.functional as F
9
+
10
+ from starVLA.model.modules.vlm.openpi_transformers.gemma.configuration_gemma import GemmaConfig
11
+ from starVLA.model.modules.vlm.openpi_transformers.gemma.modeling_gemma import GemmaForCausalLM
12
+
13
+
14
+ @dataclass
15
+ class GemmaDims:
16
+ width: int
17
+ depth: int
18
+ mlp_dim: int
19
+ num_heads: int
20
+ num_kv_heads: int
21
+ head_dim: int
22
+
23
+
24
+ def create_sinusoidal_pos_embedding(
25
+ time: torch.Tensor,
26
+ dimension: int,
27
+ min_period: float,
28
+ max_period: float,
29
+ device: torch.device,
30
+ ) -> Tensor:
31
+ if dimension % 2 != 0:
32
+ raise ValueError(f"dimension ({dimension}) must be divisible by 2")
33
+ if time.ndim != 1:
34
+ raise ValueError("time must have shape [batch_size]")
35
+
36
+ fraction = torch.linspace(0.0, 1.0, dimension // 2, dtype=torch.float32, device=device)
37
+ period = min_period * (max_period / min_period) ** fraction
38
+ scaling_factor = 1.0 / period * 2 * torch.pi
39
+ sin_input = scaling_factor[None, :] * time.to(torch.float32)[:, None]
40
+ return torch.cat([torch.sin(sin_input), torch.cos(sin_input)], dim=1)
41
+
42
+
43
+ def sample_beta(alpha: float, beta: float, bsize: int, device: torch.device) -> torch.Tensor:
44
+ alpha_t = torch.as_tensor(alpha, dtype=torch.float32, device=device)
45
+ beta_t = torch.as_tensor(beta, dtype=torch.float32, device=device)
46
+ return torch.distributions.Beta(alpha_t, beta_t).sample((bsize,))
47
+
48
+
49
+ def make_att_2d_masks(pad_masks: torch.Tensor, att_masks: torch.Tensor) -> torch.Tensor:
50
+ if att_masks.ndim != 2 or pad_masks.ndim != 2:
51
+ raise ValueError(f"Expected 2D masks, got {pad_masks.ndim=} {att_masks.ndim=}")
52
+ cumsum = torch.cumsum(att_masks, dim=1)
53
+ att_2d_masks = cumsum[:, None, :] <= cumsum[:, :, None]
54
+ pad_2d_masks = pad_masks[:, None, :] * pad_masks[:, :, None]
55
+ return att_2d_masks & pad_2d_masks
56
+
57
+
58
+ def make_att_4d_masks(att_2d_masks: torch.Tensor) -> torch.Tensor:
59
+ return torch.where(att_2d_masks[:, None, :, :], 0.0, -2.3819763e38)
60
+
61
+
62
+
63
+ class OpenPIGemma(nn.Module):
64
+ """Gemma transformer wrapper used by OpenPI action heads."""
65
+
66
+ def __init__(self, config: GemmaDims, *, use_adarms: bool = False):
67
+ super().__init__()
68
+ hf_config = GemmaConfig(
69
+ head_dim=config.head_dim,
70
+ hidden_size=config.width,
71
+ intermediate_size=config.mlp_dim,
72
+ num_attention_heads=config.num_heads,
73
+ num_hidden_layers=config.depth,
74
+ num_key_value_heads=config.num_kv_heads,
75
+ vocab_size=257152,
76
+ hidden_activation="gelu_pytorch_tanh",
77
+ torch_dtype="float32",
78
+ use_adarms=bool(use_adarms),
79
+ adarms_cond_dim=config.width if use_adarms else None,
80
+ )
81
+ gemma = GemmaForCausalLM(config=hf_config)
82
+ gemma.model.embed_tokens = None
83
+ self.model = gemma.model
84
+ self.lm_head = gemma.lm_head
85
+ self.validate_transformers_api(use_adarms=use_adarms)
86
+ if use_adarms:
87
+ self._ensure_adarms_norms(config.width)
88
+
89
+ def _ensure_adarms_norms(self, cond_dim: int) -> None:
90
+ for layer in self.model.layers:
91
+ self._ensure_adarms_norm(layer.input_layernorm, cond_dim)
92
+ self._ensure_adarms_norm(layer.post_attention_layernorm, cond_dim)
93
+ self._ensure_adarms_norm(self.model.norm, cond_dim)
94
+
95
+ @staticmethod
96
+ def _ensure_adarms_norm(norm: nn.Module, cond_dim: int) -> None:
97
+ if getattr(norm, "dense", None) is None:
98
+ width = int(norm.weight.shape[0])
99
+ norm.cond_dim = int(cond_dim)
100
+ norm.dense = nn.Linear(cond_dim, width * 3)
101
+ nn.init.zeros_(norm.dense.weight)
102
+ if hasattr(norm, "weight"):
103
+ norm.weight.data.zero_()
104
+ norm.weight.requires_grad_(False)
105
+
106
+ def validate_transformers_api(self, *, use_adarms: bool) -> None:
107
+ if not use_adarms:
108
+ return
109
+ norm_sig = inspect.signature(self.model.layers[0].input_layernorm.forward)
110
+ model_sig = inspect.signature(self.model.forward)
111
+ if "cond" not in norm_sig.parameters or "adarms_cond" not in model_sig.parameters:
112
+ raise RuntimeError(
113
+ "PI05 requires StarVLA vendored OpenPI-compatible Gemma adaRMS support. "
114
+ "The imported Gemma does not expose GemmaRMSNorm.forward(cond=...) and "
115
+ "GemmaModel.forward(adarms_cond=...). "
116
+ f"Current signatures: GemmaRMSNorm.forward{norm_sig}, GemmaModel.forward{model_sig}"
117
+ )
118
+
119
+
120
+ class OpenPIActionHeadBase(nn.Module):
121
+ """OpenPI action expert + action head.
122
+
123
+ This module owns the action-side Gemma expert, suffix embedding, action
124
+ projections, and the suffix-only denoise forward used during inference.
125
+ """
126
+
127
+ use_adarms: bool = False
128
+
129
+ def __init__(self, config: GemmaDims, *, action_dim: int, action_horizon: int, max_state_dim: int):
130
+ super().__init__()
131
+ self.action_dim = int(action_dim)
132
+ self.action_horizon = int(action_horizon)
133
+ self.max_state_dim = int(max_state_dim)
134
+ self.width = int(config.width)
135
+ self.gemma_expert = OpenPIGemma(config, use_adarms=self.use_adarms)
136
+ self.action_in_proj = nn.Linear(self.action_dim, self.width)
137
+ self.action_out_proj = nn.Linear(self.width, self.action_dim)
138
+
139
+ @property
140
+ def model(self):
141
+ return self.gemma_expert.model
142
+
143
+ @property
144
+ def layers(self):
145
+ return self.gemma_expert.model.layers
146
+
147
+ @property
148
+ def norm(self):
149
+ return self.gemma_expert.model.norm
150
+
151
+ def set_gemma_expert_eager_attention(self):
152
+ self.gemma_expert.model.config._attn_implementation = "eager"
153
+
154
+ def supports_cached_denoise(self) -> bool:
155
+ return True
156
+
157
+ def forward_gemma_expert(
158
+ self,
159
+ *,
160
+ inputs_embeds: torch.Tensor,
161
+ attention_mask: torch.Tensor | None,
162
+ position_ids: torch.LongTensor | None,
163
+ past_key_values,
164
+ use_cache: bool | None,
165
+ adarms_cond: torch.Tensor | None,
166
+ ):
167
+ kwargs = {
168
+ "inputs_embeds": inputs_embeds,
169
+ "attention_mask": attention_mask,
170
+ "position_ids": position_ids,
171
+ "past_key_values": past_key_values,
172
+ "use_cache": use_cache,
173
+ }
174
+ if "adarms_cond" in inspect.signature(self.gemma_expert.model.forward).parameters:
175
+ kwargs["adarms_cond"] = adarms_cond
176
+ elif adarms_cond is not None:
177
+ raise RuntimeError(
178
+ "PI05 requires StarVLA vendored OpenPI-compatible Gemma adaRMS support, "
179
+ "but the imported action expert does not expose it."
180
+ )
181
+ return self.gemma_expert.model.forward(**kwargs)
182
+
183
+ def sample_action_noise(self, shape, device: torch.device):
184
+ return torch.normal(0.0, 1.0, size=shape, dtype=torch.float32, device=device)
185
+
186
+ def sample_flow_time(self, bsize: int, device: torch.device):
187
+ return (sample_beta(1.5, 1.0, bsize, device) * 0.999 + 0.001).to(torch.float32)
188
+
189
+ def prepare_flow_targets(self, actions: torch.Tensor, noise=None, time=None):
190
+ actions = actions.to(device=self.action_in_proj.weight.device, dtype=torch.float32)
191
+ noise = self.sample_action_noise(actions.shape, actions.device) if noise is None else noise.to(actions.device, torch.float32)
192
+ time = self.sample_flow_time(actions.shape[0], actions.device) if time is None else time.to(actions.device, torch.float32)
193
+ x_t = time[:, None, None] * noise + (1 - time[:, None, None]) * actions
194
+ u_t = noise - actions
195
+ return x_t, u_t, noise, time
196
+
197
+ def embed_action_suffix(self, state: torch.Tensor, noisy_actions: torch.Tensor, timestep: torch.Tensor):
198
+ raise NotImplementedError
199
+
200
+ def decode_action_velocity(self, action_hidden_states: torch.Tensor) -> torch.Tensor:
201
+ action_hidden_states = action_hidden_states[:, -self.action_horizon :].to(self.action_out_proj.weight.dtype)
202
+ return self.action_out_proj(action_hidden_states).to(torch.float32)
203
+
204
+ def forward(
205
+ self,
206
+ *,
207
+ attention_mask: torch.Tensor,
208
+ position_ids: torch.LongTensor,
209
+ past_key_values,
210
+ suffix_embs: torch.Tensor,
211
+ adarms_cond: torch.Tensor | None,
212
+ force_bfloat16: bool = False,
213
+ ):
214
+ if force_bfloat16:
215
+ suffix_embs = suffix_embs.to(torch.bfloat16)
216
+ self.set_gemma_expert_eager_attention()
217
+ suffix_output = self.forward_gemma_expert(
218
+ attention_mask=attention_mask,
219
+ position_ids=position_ids,
220
+ past_key_values=past_key_values,
221
+ inputs_embeds=suffix_embs,
222
+ use_cache=False,
223
+ adarms_cond=adarms_cond,
224
+ )
225
+ suffix_out = suffix_output.last_hidden_state
226
+ return self.decode_action_velocity(suffix_out)
227
+
228
+
229
+
230
+ class OpenPI0ActionHead(OpenPIActionHeadBase):
231
+ """PI0 suffix path: state token plus action-time MLP, no adaRMS conditioning."""
232
+
233
+ use_adarms = False
234
+
235
+ def __init__(self, config: GemmaDims, *, action_dim: int, action_horizon: int, max_state_dim: int):
236
+ super().__init__(config, action_dim=action_dim, action_horizon=action_horizon, max_state_dim=max_state_dim)
237
+ self.state_proj = nn.Linear(self.max_state_dim, self.width)
238
+ self.action_time_mlp_in = nn.Linear(2 * self.width, self.width)
239
+ self.action_time_mlp_out = nn.Linear(self.width, self.width)
240
+
241
+ def embed_action_suffix(self, state: torch.Tensor, noisy_actions: torch.Tensor, timestep: torch.Tensor):
242
+ state_emb = self.state_proj(state.to(self.state_proj.weight.dtype))
243
+ embs = [state_emb[:, None, :]]
244
+ pad_masks = [torch.ones(state_emb.shape[0], 1, dtype=torch.bool, device=state_emb.device)]
245
+ att_masks = [1]
246
+
247
+ time_emb = create_sinusoidal_pos_embedding(
248
+ timestep,
249
+ self.width,
250
+ min_period=4e-3,
251
+ max_period=4.0,
252
+ device=timestep.device,
253
+ ).to(self.action_in_proj.weight.dtype)
254
+ action_emb = self.action_in_proj(noisy_actions.to(self.action_in_proj.weight.dtype))
255
+ time_emb = time_emb[:, None, :].expand_as(action_emb)
256
+ action_time_emb = self.action_time_mlp_out(
257
+ F.silu(self.action_time_mlp_in(torch.cat([action_emb, time_emb], dim=2)))
258
+ )
259
+ embs.append(action_time_emb)
260
+ pad_masks.append(torch.ones(action_time_emb.shape[:2], dtype=torch.bool, device=action_time_emb.device))
261
+ att_masks += [1] + ([0] * (self.action_horizon - 1))
262
+ embs = torch.cat(embs, dim=1)
263
+ pad_masks = torch.cat(pad_masks, dim=1)
264
+ att_masks = torch.tensor(att_masks, dtype=torch.bool, device=embs.device)[None, :]
265
+ return embs, pad_masks, att_masks.expand(embs.shape[0], -1), None
266
+
267
+
268
+ class OpenPI05ActionHead(OpenPIActionHeadBase):
269
+ """PI05 suffix path: action tokens conditioned by adaRMS time embedding."""
270
+
271
+ use_adarms = True
272
+
273
+ def __init__(self, config: GemmaDims, *, action_dim: int, action_horizon: int, max_state_dim: int):
274
+ super().__init__(config, action_dim=action_dim, action_horizon=action_horizon, max_state_dim=max_state_dim)
275
+ self.time_mlp_in = nn.Linear(self.width, self.width)
276
+ self.time_mlp_out = nn.Linear(self.width, self.width)
277
+
278
+ def embed_action_suffix(self, state: torch.Tensor, noisy_actions: torch.Tensor, timestep: torch.Tensor):
279
+ time_emb = create_sinusoidal_pos_embedding(
280
+ timestep,
281
+ self.width,
282
+ min_period=4e-3,
283
+ max_period=4.0,
284
+ device=timestep.device,
285
+ ).to(self.time_mlp_in.weight.dtype)
286
+ action_time_emb = self.action_in_proj(noisy_actions.to(self.action_in_proj.weight.dtype))
287
+ adarms_cond = F.silu(self.time_mlp_out(F.silu(self.time_mlp_in(time_emb))))
288
+ pad_masks = torch.ones(action_time_emb.shape[:2], dtype=torch.bool, device=action_time_emb.device)
289
+ att_masks = torch.tensor([1] + ([0] * (self.action_horizon - 1)), dtype=torch.bool, device=action_time_emb.device)
290
+ return action_time_emb, pad_masks, att_masks[None, :].expand(action_time_emb.shape[0], -1), adarms_cond
starVLA/model/modules/action_model/__init__.py CHANGED
@@ -1 +1,2 @@
 
1
  """Action models included in the Florence-Bridge OPTR release."""
 
1
+ # Modified in the ReTime research artifact; see NOTICE.
2
  """Action models included in the Florence-Bridge OPTR release."""
starVLA/model/modules/action_model/flow_matching_head/cross_attention_dit.py CHANGED
@@ -1,3 +1,4 @@
 
1
  # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
  # SPDX-License-Identifier: Apache-2.0
3
  #
@@ -166,7 +167,7 @@ class BasicTransformerBlock(nn.Module):
166
  attn_output = self.attn1(
167
  norm_hidden_states,
168
  encoder_hidden_states=encoder_hidden_states,
169
- attention_mask=encoder_attention_mask, # @JinhuiYE original attention_mask=attention_mask
170
  )
171
  if self.final_dropout:
172
  attn_output = self.final_dropout(attn_output)
@@ -276,7 +277,7 @@ class DiT(ModelMixin, ConfigMixin):
276
  hidden_states = hidden_states.contiguous()
277
  encoder_hidden_states = encoder_hidden_states.contiguous()
278
 
279
- all_hidden_states = [hidden_states]
280
 
281
  # Process through transformer blocks
282
  for idx, block in enumerate(self.transformer_blocks):
@@ -296,7 +297,8 @@ class DiT(ModelMixin, ConfigMixin):
296
  encoder_attention_mask=encoder_attention_mask,
297
  temb=temb,
298
  )
299
- all_hidden_states.append(hidden_states)
 
300
 
301
  # Output processing
302
  conditioning = temb
@@ -364,12 +366,13 @@ class SelfAttentionTransformer(ModelMixin, ConfigMixin):
364
  ):
365
  # Process through transformer blocks - single pass through the blocks
366
  hidden_states = hidden_states.contiguous()
367
- all_hidden_states = [hidden_states]
368
 
369
  # Process through transformer blocks
370
  for idx, block in enumerate(self.transformer_blocks):
371
  hidden_states = block(hidden_states)
372
- all_hidden_states.append(hidden_states)
 
373
 
374
  if return_all_hidden_states:
375
  return hidden_states, all_hidden_states
 
1
+ # Modified in the ReTime research artifact; see NOTICE.
2
  # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
  # SPDX-License-Identifier: Apache-2.0
4
  #
 
167
  attn_output = self.attn1(
168
  norm_hidden_states,
169
  encoder_hidden_states=encoder_hidden_states,
170
+ attention_mask=encoder_attention_mask,
171
  )
172
  if self.final_dropout:
173
  attn_output = self.final_dropout(attn_output)
 
277
  hidden_states = hidden_states.contiguous()
278
  encoder_hidden_states = encoder_hidden_states.contiguous()
279
 
280
+ all_hidden_states = [hidden_states] if return_all_hidden_states else None
281
 
282
  # Process through transformer blocks
283
  for idx, block in enumerate(self.transformer_blocks):
 
297
  encoder_attention_mask=encoder_attention_mask,
298
  temb=temb,
299
  )
300
+ if all_hidden_states is not None:
301
+ all_hidden_states.append(hidden_states)
302
 
303
  # Output processing
304
  conditioning = temb
 
366
  ):
367
  # Process through transformer blocks - single pass through the blocks
368
  hidden_states = hidden_states.contiguous()
369
+ all_hidden_states = [hidden_states] if return_all_hidden_states else None
370
 
371
  # Process through transformer blocks
372
  for idx, block in enumerate(self.transformer_blocks):
373
  hidden_states = block(hidden_states)
374
+ if all_hidden_states is not None:
375
+ all_hidden_states.append(hidden_states)
376
 
377
  if return_all_hidden_states:
378
  return hidden_states, all_hidden_states