Complete shared baseline runtime dependencies
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- external_baselines/frameskip_adapted/build_required_draws.py +0 -15
- external_baselines/frameskip_adapted/finalize_cache.py +1 -11
- external_baselines/frameskip_adapted/train_frameskip_continuation.py +1 -1
- external_baselines/frameskip_robotwin_adapted/build_robotwin_cache.py +211 -0
- external_baselines/frameskip_robotwin_adapted/train_frameskip_robotwin_continuation.py +120 -0
- external_baselines/frameskip_robotwin_adapted/validate_assets.py +68 -0
- external_baselines/keyframe_focused_adapted/build_ape_cache.py +485 -0
- external_baselines/keyframe_focused_adapted/configs/keyframe_focused_seed117465_20k_to_20500.yaml +118 -0
- external_baselines/keyframe_focused_adapted/keyframe_cache.py +145 -0
- external_baselines/keyframe_focused_adapted/train_keyframe_continuation.py +85 -0
- external_baselines/keyframe_focused_adapted/validate_cache_support.py +105 -0
- external_baselines/loss_is_active/train_active_loss_is_continuation.py +330 -0
- external_baselines/loss_is_adapted/configs/loss_is_seed117465_20k_to_20500.yaml +122 -0
- external_baselines/loss_is_adapted/loss_is.py +48 -0
- external_baselines/loss_is_adapted/train_loss_is_continuation.py +293 -0
- external_baselines/qwen_adapted/train_qwen_frameskip_continuation.py +129 -0
- external_baselines/qwen_robotwin_adapted/register_qwen_robotwin_smoke.py +17 -0
- external_baselines/qwen_robotwin_adapted/train_qwen_robotwin_frameskip.py +22 -0
- external_baselines/qwen_robotwin_adapted/train_qwen_robotwin_keyframe.py +22 -0
- external_baselines/qwen_robotwin_adapted/train_qwen_robotwin_loss_is.py +22 -0
- external_baselines/settings/florence_bridge/frameskip.yaml +71 -0
- external_baselines/settings/florence_bridge/keyframe.yaml +70 -0
- external_baselines/settings/florence_bridge/loss_is_active.yaml +117 -0
- external_baselines/settings/florence_robotwin/frameskip.yaml +109 -0
- external_baselines/settings/florence_robotwin/keyframe.yaml +110 -0
- external_baselines/settings/florence_robotwin/loss_is_active.yaml +114 -0
- external_baselines/settings/qwen_bridge/frameskip.yaml +93 -0
- external_baselines/settings/qwen_bridge/keyframe.yaml +94 -0
- external_baselines/settings/qwen_bridge/loss_is_active.yaml +99 -0
- external_baselines/settings/qwen_robotwin/frameskip.yaml +107 -0
- external_baselines/settings/qwen_robotwin/keyframe.yaml +108 -0
- external_baselines/settings/qwen_robotwin/loss_is_active.yaml +112 -0
- starVLA/dataloader/__init__.py +2 -7
- starVLA/dataloader/gr00t_lerobot/data_config.py +3 -2
- starVLA/dataloader/gr00t_lerobot/datasets.py +62 -37
- starVLA/dataloader/gr00t_lerobot/transform/state_action.py +1 -0
- starVLA/dataloader/infoflow_sampler.py +4 -338
- starVLA/dataloader/lerobot_datasets.py +2 -3
- starVLA/dataloader/relative_pose.py +96 -0
- starVLA/dataloader/temporal_proposal.py +4 -227
- starVLA/model/framework/VLM4A/PI0.py +653 -0
- starVLA/model/framework/VLM4A/PI05.py +58 -0
- starVLA/model/framework/VLM4A/QwenFast.py +174 -0
- starVLA/model/framework/VLM4A/QwenGR00T.py +38 -3
- starVLA/model/framework/base_framework.py +2 -1
- starVLA/model/framework/share_tools.py +6 -15
- starVLA/model/modules/action_model/GR00T_ActionHeader.py +24 -2
- starVLA/model/modules/action_model/OpenPI_ActionHead.py +290 -0
- starVLA/model/modules/action_model/__init__.py +1 -0
- 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
|
| 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 |
-
"
|
| 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 |
-
|
| 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",#
|
| 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", #
|
| 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 |
-
#
|
| 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 |
-
-
|
|
|
|
| 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 =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
#
|
| 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
|
| 2550 |
-
|
| 2551 |
-
|
| 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
|
| 2567 |
-
|
| 2568 |
-
|
| 2569 |
-
|
| 2570 |
-
|
| 2571 |
-
|
| 2572 |
-
|
| 2573 |
-
|
| 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),
|
| 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 |
-
"""
|
| 2 |
|
| 3 |
-
from
|
|
|
|
| 4 |
|
| 5 |
-
|
| 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 |
-
"""
|
| 2 |
|
| 3 |
-
from
|
|
|
|
| 4 |
|
| 5 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 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"
|
| 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"
|
| 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"
|
| 398 |
raise FileNotFoundError(f"Pretrained checkpoint `{pretrained_checkpoint}` does not exist.")
|
| 399 |
return global_cfg, norm_stats
|
| 400 |
|
| 401 |
|
| 402 |
-
#
|
| 403 |
-
#
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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,
|
| 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
|
|
|
|
| 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
|
|
|
|
| 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
|