mmmmmcyaaa commited on
Commit
977b06e
·
verified ·
1 Parent(s): c3a8443

Add Florence-Bridge code and handoff documentation

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +31 -0
  2. .pytest_cache/.gitignore +2 -0
  3. .pytest_cache/CACHEDIR.TAG +4 -0
  4. .pytest_cache/README.md +8 -0
  5. .pytest_cache/v/cache/lastfailed +1 -0
  6. .pytest_cache/v/cache/nodeids +17 -0
  7. .pytest_cache/v/cache/stepwise +1 -0
  8. AGENTS.md +67 -0
  9. ASSET_MANIFEST.json +22 -0
  10. CITATION.cff +28 -0
  11. EXPERIMENT_CONTRACT.md +96 -0
  12. LICENSE +24 -0
  13. README.md +61 -0
  14. SOURCE_INVENTORY.md +40 -0
  15. configs/deepspeed_florence_2gpu.yaml +15 -0
  16. configs/ds_florence_zero2.json +19 -0
  17. configs/florence_bridge_20k_plus500.yaml +110 -0
  18. configs/reference/common_start_20k_full.yaml +113 -0
  19. configs/reference/residual_seed117465_full.yaml +118 -0
  20. configs/reference/uniform_seed117465_full.yaml +118 -0
  21. deployment/__init__.py +0 -0
  22. deployment/model_server/__init__.py +0 -0
  23. deployment/model_server/policy_norm_processor.py +396 -0
  24. deployment/model_server/policy_wrapper.py +174 -0
  25. deployment/model_server/server_policy.py +69 -0
  26. deployment/model_server/tools/__init__.py +0 -0
  27. deployment/model_server/tools/image_tools.py +123 -0
  28. deployment/model_server/tools/msgpack_numpy.py +57 -0
  29. deployment/model_server/tools/websocket_policy_client.py +77 -0
  30. deployment/model_server/tools/websocket_policy_server.py +161 -0
  31. examples/SimplerEnv/README.md +158 -0
  32. examples/SimplerEnv/eval_files/adaptive_ensemble.py +45 -0
  33. examples/SimplerEnv/eval_files/custom_argparse.py +143 -0
  34. examples/SimplerEnv/eval_files/model2simpler_interface.py +276 -0
  35. examples/SimplerEnv/eval_files/start_simpler_env.py +63 -0
  36. examples/SimplerEnv/eval_files/test_your_simplerEnv.py +43 -0
  37. examples/SimplerEnv/train_files/data_registry/data_config.py +197 -0
  38. examples/SimplerEnv/train_files/fractal_modality.json +74 -0
  39. examples/SimplerEnv/train_files/modality.json +74 -0
  40. experiments/importance_correction_ablation/train_residual_without_pq.py +26 -0
  41. external_baselines/frameskip_adapted/README.md +26 -0
  42. external_baselines/frameskip_adapted/build_cache_shard.py +219 -0
  43. external_baselines/frameskip_adapted/build_required_draws.py +96 -0
  44. external_baselines/frameskip_adapted/finalize_cache.py +97 -0
  45. external_baselines/frameskip_adapted/train_frameskip_continuation.py +104 -0
  46. pyproject.toml +89 -0
  47. requirements.txt +41 -0
  48. results/formal_384/aggregate_summary.json +173 -0
  49. results/formal_384/residual/policy_seed_516408/carrot/result.json +63 -0
  50. results/formal_384/residual/policy_seed_516408/eggplant/result.json +63 -0
.gitignore ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.so
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+ .venv/
8
+ venv/
9
+ .env
10
+ .env.*
11
+ wandb/
12
+ logs/
13
+ outputs/
14
+ runs/
15
+ checkpoints/
16
+ cache/
17
+ artifacts/
18
+ *.pt
19
+ *.pth
20
+ *.ckpt
21
+ *.safetensors
22
+ *.mp4
23
+ *.parquet
24
+ *.hdf5
25
+ *.jsonl
26
+ *.tmp
27
+ *.orig
28
+ *.patch
29
+ .DS_Store
30
+ .idea/
31
+ .vscode/
.pytest_cache/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Created by pytest automatically.
2
+ *
.pytest_cache/CACHEDIR.TAG ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Signature: 8a477f597d28d172789f06886806bc55
2
+ # This file is a cache directory tag created by pytest.
3
+ # For information about cache directory tags, see:
4
+ # https://bford.info/cachedir/spec.html
.pytest_cache/README.md ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # pytest cache directory #
2
+
3
+ This directory contains data from the pytest's cache plugin,
4
+ which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
5
+
6
+ **Do not** commit this to version control.
7
+
8
+ See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information.
.pytest_cache/v/cache/lastfailed ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
.pytest_cache/v/cache/nodeids ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ "tests/infoflow/test_objective_preservation.py::test_alpha_zero_is_exact_baseline_identity",
3
+ "tests/infoflow/test_objective_preservation.py::test_finite_support_objective_and_gradient_are_exactly_preserved",
4
+ "tests/infoflow/test_objective_preservation.py::test_fixed_seed_monte_carlo_estimator_matches_baseline",
5
+ "tests/infoflow/test_objective_preservation.py::test_flow_loss_reduction_preserves_legacy_scalar_mean",
6
+ "tests/infoflow/test_objective_preservation.py::test_invalid_reduction_and_repeat_shapes_fail_fast",
7
+ "tests/infoflow/test_objective_preservation.py::test_invalid_safe_mixture_inputs_fail_fast[base0-proposal0-0.5-ValueError]",
8
+ "tests/infoflow/test_objective_preservation.py::test_invalid_safe_mixture_inputs_fail_fast[base1-proposal1-0.5-ValueError]",
9
+ "tests/infoflow/test_objective_preservation.py::test_invalid_safe_mixture_inputs_fail_fast[base2-proposal2-0.5-ValueError]",
10
+ "tests/infoflow/test_objective_preservation.py::test_invalid_safe_mixture_inputs_fail_fast[base3-proposal3-1.0-ValueError]",
11
+ "tests/infoflow/test_objective_preservation.py::test_invalid_safe_mixture_inputs_fail_fast[base4-proposal4--0.1-ValueError]",
12
+ "tests/infoflow/test_objective_preservation.py::test_ordinary_estimator_is_not_batch_self_normalized_and_stops_weight_gradient",
13
+ "tests/infoflow/test_objective_preservation.py::test_repeated_flow_losses_are_aggregated_per_original_sample",
14
+ "tests/infoflow/test_objective_preservation.py::test_repeated_per_sample_path_matches_legacy_scalar_and_gradient",
15
+ "tests/infoflow/test_objective_preservation.py::test_safe_mixture_normalization_support_and_bound",
16
+ "tests/infoflow/test_trainer_accumulation.py::test_train_step_preserves_earlier_microbatch_gradients"
17
+ ]
.pytest_cache/v/cache/stepwise ADDED
@@ -0,0 +1 @@
 
 
1
+ []
AGENTS.md ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AGENTS.md — Florence–Bridge OPTR
2
+
3
+ ## Mission
4
+
5
+ Reproduce or extend Florence–Bridge continuations without changing the experimental contract. Treat `EXPERIMENT_CONTRACT.md` and the downloaded 20K state as authoritative.
6
+
7
+ ## Non-negotiable rules
8
+
9
+ - Do not use Git operations for this handoff.
10
+ - Never overwrite the 20K common start, frozen proposal, or published 20.5K snapshots.
11
+ - New runs use new output directories.
12
+ - Resume new branches from the complete 20K Accelerate state, never from a 20.5K model-only file.
13
+ - Do not add code, path, manifest, or self-referential checksum launch gates.
14
+ - Keep dataset, episode, valid-anchor and proposal-support identity checks.
15
+ - Checkpoint readiness is established by required-file presence, byte size, loadability and semantic contract checks.
16
+ - Do not silently change world size, global batch, continuation seed, proposal, correction, budget or evaluation panel.
17
+
18
+ ## Required local assets
19
+
20
+ Set these variables before launch:
21
+
22
+ ```bash
23
+ export OPTR_PROJECT_ROOT=/path/to/this/repository
24
+ export OPTR_RUN_ROOT=/path/to/new/runs
25
+ export OPTR_PYTHON=/path/to/python
26
+ export FLORENCE_MODEL_PATH=/path/to/Florence-2-large
27
+ export BRIDGE_DATA_ROOT=/path/to/OXE_LEROBOT_DATASET
28
+ export BRIDGE_STATISTICS_PATH=/path/to/dataset_statistics.json
29
+ export BRIDGE_PROVENANCE_PATH=$OPTR_PROJECT_ROOT/manifests/data_provenance.json
30
+ export BRIDGE_FINGERPRINT_PATH=$OPTR_PROJECT_ROOT/manifests/content_fingerprints.json
31
+ export BRIDGE_MANIFEST_PATH=$OPTR_PROJECT_ROOT/manifests/valid_anchor_manifest_florence_scratch_v1.json
32
+ export COMMON_START_20K=$OPTR_PROJECT_ROOT/common_start_20k/checkpoints/steps_20000_accelerate
33
+ ```
34
+
35
+ ## Locked training contract
36
+
37
+ - Base: Florence-2-large; action head: DiT-B; horizon 16; state/action dim 7.
38
+ - Two GPUs, BF16, DeepSpeed ZeRO-2.
39
+ - Micro-batch 4/GPU × world size 2 × accumulation 8 = global batch 64.
40
+ - Parent seed 42; continuation/sampler seed 117465; model RNG 588643; transform RNG 298226.
41
+ - Resume step 20,000; stop and save at step 20,500.
42
+ - AdamW `(0.9, 0.95)`, eps `1e-8`, weight decay `1e-8`; clipping 1.0.
43
+ - Cosine scheduler, 5K warmup, min LR `5e-7`; preserve restored scheduler state.
44
+ - Residual: K=8, alpha=0.2, epsilon=0.1, linear within-episode interpolation, ordinary p/q correction.
45
+
46
+ ## Commands
47
+
48
+ ```bash
49
+ bash scripts/run_florence_bridge_arm.sh uniform <new-run-id>
50
+ bash scripts/run_florence_bridge_arm.sh residual <new-run-id> \
51
+ proposals/residual_temporal_proposal_k8_alpha0.2.json
52
+ ```
53
+
54
+ After start, verify exact resume at 20K, global batch 64, and the first update above 20K. A valid finish has exit code 0 and a complete step-20500 model snapshot.
55
+
56
+ ## Evaluation
57
+
58
+ Use `scripts/evaluate_bridge_384_no_video.sh`. The locked panel has policy seeds 518957/765353/686765/516408; tasks Stack/Carrot/Spoon/Eggplant; 24 episodes per seed-task; BF16; no video; 384 rollouts per checkpoint. Preserve raw episode outcomes.
59
+
60
+ ## Where to look first
61
+
62
+ - Contract and factual provenance: `EXPERIMENT_CONTRACT.md`
63
+ - File roles and sizes: `ASSET_MANIFEST.json`
64
+ - Source authority and known corrections: `SOURCE_INVENTORY.md`
65
+ - Training config: `configs/florence_bridge_20k_plus500.yaml`
66
+ - Sampling implementation: `starVLA/dataloader/temporal_proposal.py`, `infoflow_sampler.py`
67
+ - Training integration: `starVLA/training/infoflow.py`, `train_starvla.py`
ASSET_MANIFEST.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": 1,
3
+ "purpose": "Florence-Bridge public handoff; entries describe file roles and expected byte sizes",
4
+ "repository": "mmmmmcyaaa/ReTime",
5
+ "assets": [
6
+ {"path": "common_start_20k/checkpoints/steps_20000_accelerate/checkpoint.json", "bytes": 1164, "role": "common-start metadata"},
7
+ {"path": "common_start_20k/checkpoints/steps_20000_accelerate/latest", "bytes": 13, "role": "DeepSpeed latest tag"},
8
+ {"path": "common_start_20k/checkpoints/steps_20000_accelerate/zero_to_fp32.py", "bytes": 33270, "role": "DeepSpeed conversion helper"},
9
+ {"path": "common_start_20k/checkpoints/steps_20000_accelerate/random_states_0.pkl", "bytes": 15017, "role": "rank-0 RNG state"},
10
+ {"path": "common_start_20k/checkpoints/steps_20000_accelerate/random_states_1.pkl", "bytes": 15081, "role": "rank-1 RNG state"},
11
+ {"path": "common_start_20k/checkpoints/steps_20000_accelerate/pytorch_model/mp_rank_00_model_states.pt", "bytes": 1839089981, "role": "DeepSpeed model state"},
12
+ {"path": "common_start_20k/checkpoints/steps_20000_accelerate/pytorch_model.pt", "bytes": 2154184469, "role": "consolidated 20K model"},
13
+ {"path": "common_start_20k/checkpoints/steps_20000_accelerate/pytorch_model/bf16_zero_pp_rank_0_mp_rank_00_optim_states.pt", "bytes": 5514449457, "role": "ZeRO-2 optimizer rank 0"},
14
+ {"path": "common_start_20k/checkpoints/steps_20000_accelerate/pytorch_model/bf16_zero_pp_rank_1_mp_rank_00_optim_states.pt", "bytes": 5514463601, "role": "ZeRO-2 optimizer rank 1"},
15
+ {"path": "checkpoints/residual/steps_20500_pytorch_model.pt", "bytes": 2154184469, "role": "Residual-OPTR model-only evaluation snapshot"},
16
+ {"path": "checkpoints/uniform/steps_20500_pytorch_model.pt", "bytes": 2154184469, "role": "Uniform model-only evaluation snapshot"},
17
+ {"path": "proposals/residual_temporal_proposal_k8_alpha0.2.json", "bytes": 132329699, "role": "frozen Residual proposal"},
18
+ {"path": "manifests/data_provenance.json", "bytes": 651, "role": "dataset provenance"},
19
+ {"path": "manifests/content_fingerprints.json", "bytes": 53500547, "role": "dataset content identity"},
20
+ {"path": "manifests/valid_anchor_manifest_florence_scratch_v1.json", "bytes": 101950249, "role": "valid-anchor identity"}
21
+ ]
22
+ }
CITATION.cff ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This CITATION.cff file was generated with cffinit.
2
+ # Visit https://bit.ly/cffinit to generate yours today!
3
+
4
+ cff-version: 1.2.0
5
+ title: >-
6
+ StarVLA: A Lego-like Codebase for Vision-Language-Action
7
+ Model Developing
8
+ message: >-
9
+ If you use this software, please cite it using the
10
+ metadata from this file.
11
+ type: software
12
+ authors:
13
+ - name: starVLA Community
14
+ identifiers:
15
+ - type: doi
16
+ value: 10.5281/zenodo.18264214
17
+ repository-code: 'https://github.com/starVLA/starVLA'
18
+ url: 'https://starvla.github.io/'
19
+ abstract: >-
20
+ A modular, Lego-like codebase for developing
21
+ Vision-Language-Action models.
22
+ keywords:
23
+ - vision-language-action
24
+ - robot-learning
25
+ - modular-framework
26
+ license: MIT
27
+ version: 1.2.0
28
+ date-released: '2026-01-15'
EXPERIMENT_CONTRACT.md ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Florence–Bridge experiment contract
2
+
3
+ ## 1. Scope
4
+
5
+ This handoff covers exactly two published arms from one complete common start:
6
+
7
+ 1. Uniform continuation, 20K → 20.5K.
8
+ 2. Residual-OPTR continuation, K=8, alpha=0.2, with ordinary p/q correction, 20K → 20.5K.
9
+
10
+ Matched-Random/shuffle is intentionally excluded from the uploaded checkpoint set.
11
+
12
+ ## 2. Common start and resume semantics
13
+
14
+ The parent is the Florence–Bridge 20K complete Accelerate checkpoint produced by the seed-42 scratch run. It contains the consolidated model, DeepSpeed ZeRO-2 optimizer shards, model-state shard, per-rank RNG states, `latest`, `checkpoint.json`, and `zero_to_fp32.py`.
15
+
16
+ The continuation restores model, optimizer, scheduler position, RNG streams, data order and update counter. It starts at completed step 20,000 and performs exactly 500 optimizer updates. The active learning rates at resume were `9.407412610098572e-06` for the VLM interface and `9.407412610098571e-05` for the action model. The restored scheduler was `LambdaLR(last_epoch=20000, step_count=20001)`.
17
+
18
+ The published 20.5K Uniform and Residual files are model-only evaluation snapshots. They are not substitutes for the full parent state.
19
+
20
+ ## 3. Model and optimization
21
+
22
+ | Field | Value |
23
+ |---|---|
24
+ | Base VLM | Florence-2-large |
25
+ | Registry name | `QwenGR00T` (compatibility name only) |
26
+ | VLM hidden dim | 2048 |
27
+ | Action head | DiT-B, hidden/action hidden 1024 |
28
+ | Cross-attention dim | 1024 |
29
+ | Action/state dim | 7 / 7 |
30
+ | Horizon | 16 (future window 15) |
31
+ | DiT layers/dropout | 16 / 0.2 |
32
+ | Diffusion steps | model 8; training repeated steps 4; inference steps 4 |
33
+ | Precision/distribution | BF16, 2 GPUs, DeepSpeed ZeRO-2, no CPU offload |
34
+ | Batch | 4 per GPU × 2 GPUs × accumulation 8 = global 64 |
35
+ | Optimizer | AdamW, betas (0.9, 0.95), eps 1e-8, weight decay 1e-8 |
36
+ | Nominal LR | VLM interface 1e-5; action model 1e-4; base field 3e-5 |
37
+ | Scheduler | cosine-with-min-LR, warmup 5000, min LR 5e-7 |
38
+ | Gradient clipping | 1.0 |
39
+ | Seeds | parent 42; continuation/sampler 117465; model 588643; transform 298226 |
40
+ | Logging | every 10 steps in the portable handoff; historical main run logged every 50 |
41
+
42
+ No explicit `freeze_modules` or `trainable_modules` filter was configured. The optimizer contained the VLM-interface and action-model groups. In the actually executed action path, Florence vision/text interface parameters and the action head changed; decoder parameters outside that path received no update. Do not summarize this as “the entire VLM was frozen” or “every registered parameter was actively trained.”
43
+
44
+ ## 4. Data and proposal
45
+
46
+ - Data mix: `bridge_rt_infoflow` (Bridge + RT1), OXE LeRobot layout.
47
+ - Action type/mode: `delta_ee` / `abs`.
48
+ - Image size: 224×224; video backend: `torchvision_av`.
49
+ - Source and trajectory allocation remain those of the common-start data contract.
50
+ - Residual proposal: schema 1, K=8 prospective grid, linear interpolation within each episode, percentile normalization within episode after interpolation, epsilon=0.1, alpha=0.2.
51
+ - Gate42F status: PASS. The proposal changes encounter frequency; ordinary p/q correction preserves the target objective.
52
+ - Valid-anchor, episode and proposal support identity are semantic requirements. Code location and absolute paths are not.
53
+
54
+ ## 5. Fixed formal evaluation
55
+
56
+ Official SimplerEnv WidowX/Bridge setup, BF16 inference, no video:
57
+
58
+ - policy seeds: 518957, 765353, 686765, 516408;
59
+ - tasks: Stack, Carrot, Spoon, Eggplant;
60
+ - 24 episodes per policy-seed/task;
61
+ - 96 rollouts per task and per seed; 384 per checkpoint.
62
+
63
+ ### Uniform
64
+
65
+ | Policy seed | Success |
66
+ |---|---:|
67
+ | 518957 | 41/96 |
68
+ | 765353 | 39/96 |
69
+ | 686765 | 43/96 |
70
+ | 516408 | 39/96 |
71
+
72
+ Tasks: Stack 13/96, Carrot 31/96, Spoon 50/96, Eggplant 68/96. Overall: **162/384 = 42.1875%**.
73
+
74
+ ### Residual-OPTR
75
+
76
+ | Policy seed | Success |
77
+ |---|---:|
78
+ | 518957 | 50/96 |
79
+ | 765353 | 60/96 |
80
+ | 686765 | 59/96 |
81
+ | 516408 | 59/96 |
82
+
83
+ Tasks: Stack 24/96, Carrot 45/96, Spoon 83/96, Eggplant 76/96. Overall: **228/384 = 59.375%**.
84
+
85
+ ## 6. Reproduction acceptance checks
86
+
87
+ A reproduction is comparable only if it verifies:
88
+
89
+ 1. the full 20K state loads and the update counter resumes at 20K;
90
+ 2. world size 2 and global batch 64;
91
+ 3. dataset/episode/valid-anchor identity and proposal full support;
92
+ 4. proposal weights are p/q for the Residual arm;
93
+ 5. step 20500 is saved cleanly;
94
+ 6. formal evaluation contains exactly 384 raw outcomes with the locked seeds/tasks.
95
+
96
+ Do not introduce code/path checksums, self-referential manifest checks, or migration-file identity gates.
LICENSE ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) StarVLA Team.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ Rebases are allowed for forks and feature branches. When rebasing from upstream StarVLA, use descriptive commit messages, e.g., "chore: clone from StarVLA".
16
+ Preserve attribution: keep at least the two latest upstream StarVLA commits as separate.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE
README.md ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ library_name: pytorch
4
+ tags:
5
+ - robotics
6
+ - vision-language-action
7
+ - florence-2
8
+ - bridge
9
+ - importance-sampling
10
+ ---
11
+
12
+ # ReTime / OPTR — Florence–Bridge handoff
13
+
14
+ This package contains the server-authoritative Florence–Bridge OPTR code, a complete 20K common-start training state, and the Uniform/Residual-OPTR 20.5K evaluation snapshots.
15
+
16
+ ## What is included
17
+
18
+ - `common_start_20k/checkpoints/steps_20000_accelerate/`: **full resumable state** (model, optimizer, scheduler metadata and per-rank RNG state). Start every new continuation arm here.
19
+ - `checkpoints/uniform/steps_20500_pytorch_model.pt`: model-only Uniform +500 snapshot.
20
+ - `checkpoints/residual/steps_20500_pytorch_model.pt`: model-only Residual-OPTR +500 snapshot.
21
+ - `proposals/residual_temporal_proposal_k8_alpha0.2.json`: frozen K=8, alpha=0.2 Residual proposal.
22
+ - `manifests/`: Bridge/RT1 provenance, content fingerprints and valid-anchor identity.
23
+ - `configs/`, `scripts/`, `starVLA/`: portable training and evaluation code.
24
+ - `EXPERIMENT_CONTRACT.md`: exact experimental contract and results.
25
+ - `AGENTS.md`: fast handoff instructions for an AI coding agent.
26
+
27
+ The 20.5K files are sufficient for evaluation but **not** for exact optimizer/RNG continuation. Any new branch must resume from the complete 20K state.
28
+
29
+ ## Headline result
30
+
31
+ Fixed no-video SimplerEnv WidowX/Bridge panel: four policy seeds (`518957`, `765353`, `686765`, `516408`) × four tasks × 24 episodes = 384 rollouts.
32
+
33
+ | Method | Stack | Carrot | Spoon | Eggplant | Overall |
34
+ |---|---:|---:|---:|---:|---:|
35
+ | Uniform +500 | 13/96 | 31/96 | 50/96 | 68/96 | 162/384 = 42.19% |
36
+ | Residual-OPTR +500 | 24/96 | 45/96 | 83/96 | 76/96 | 228/384 = 59.38% |
37
+
38
+ ## Quick start
39
+
40
+ 1. Download this repository and install `requirements.txt` in the same software environment used for StarVLA.
41
+ 2. Download Florence-2-large and the OXE LeRobot Bridge/RT1 data.
42
+ 3. Point the environment variables listed in `AGENTS.md` to those local assets.
43
+ 4. Keep the contract fixed: two GPUs, BF16, ZeRO-2, micro-batch 4/GPU, accumulation 8, global batch 64, continuation seed 117465.
44
+ 5. Run one arm:
45
+
46
+ ```bash
47
+ bash scripts/run_florence_bridge_arm.sh uniform uniform-reproduction
48
+ bash scripts/run_florence_bridge_arm.sh residual residual-reproduction \
49
+ proposals/residual_temporal_proposal_k8_alpha0.2.json
50
+ ```
51
+
52
+ The launch path performs only file-presence and semantic data/support checks.
53
+
54
+ ## Important implementation facts
55
+
56
+ - `framework.name: QwenGR00T` is a historical registry name; the base VLM is Florence-2-large.
57
+ - The actual Florence–Bridge run used `cross_attention_dim: 1024` and DeepSpeed ZeRO-2.
58
+ - The optimizer groups are the VLM interface and action model. No explicit freeze list was configured; the executed action path updated the Florence vision/text interface and action head, while decoder parameters outside that path did not receive updates.
59
+ - Residual sampling uses ordinary `p/q` objective correction. This release intentionally does not upload Matched-Random/shuffle assets.
60
+
61
+ See `SOURCE_INVENTORY.md` before changing any method or training field.
SOURCE_INVENTORY.md ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Source inventory and authority
2
+
3
+ ## Authority order
4
+
5
+ 1. Server run artifacts and full configs on `jlu66`.
6
+ 2. This corrected handoff package.
7
+ 3. Public development repositories: [ReTime_source](https://github.com/miaochengy-pngu/ReTime_source) and [OPTR-StarVLA](https://github.com/miaochengy-pngu/OPTR-StarVLA).
8
+
9
+ Server facts override stale prose or inferred settings in a repository snapshot.
10
+
11
+ ## Server-authoritative origins
12
+
13
+ - Development tree: `/home/miaochengyu/starVLA-infoflow-codex`
14
+ - Curated source tree: `/home/miaochengyu/OPTR-StarVLA-clean`
15
+ - 20K parent: `/mnt/miaochengyu/starvla-infoflow-runtime/runs/florence-scratch-official-100k/florence-scratch-uniform-seed-42-official100k-mb4-eval1k-20260726`
16
+ - Confirmatory arms: `/mnt/miaochengyu/starvla-infoflow-runtime/runs/infoflow-confirmatory-22k-20260730`
17
+ - Frozen proposal: `/mnt/miaochengyu/starvla-infoflow-runtime/runs/infoflow-proposals/scratch20k_residual_grid8_gate42f_batch1_20260729/residual_temporal_proposal.json`
18
+ - Formal aggregate: `/home/miaochengyu/starVLA-infoflow-codex/logs/bridgert/infoflow_confirmatory_eval/seed_117465/final_test_policy_seeds_20p5/aggregate_summary.json`
19
+
20
+ ## Corrections applied during handoff
21
+
22
+ - Corrected DeepSpeed from a stale ZeRO-3 description to the actually used **ZeRO-2** configuration.
23
+ - Corrected `diffusion_model_cfg.cross_attention_dim` from stale 2048 to the actually used **1024**.
24
+ - Fixed world size directly at 2 in the portable Accelerate config.
25
+ - Restricted the public launcher to the uploaded Uniform and Residual arms.
26
+ - Removed engineering checksum/path gates from launch.
27
+ - Kept the real semantic identity requirements for data, episodes, valid anchors and proposal support.
28
+
29
+ ## Key implementation files
30
+
31
+ - `starVLA/training/train_starvla.py`: trainer, exact-state resume, checkpointing.
32
+ - `starVLA/training/infoflow.py`: InfoFlow/importance-correction integration.
33
+ - `starVLA/dataloader/temporal_proposal.py`: proposal parsing and support semantics.
34
+ - `starVLA/dataloader/infoflow_sampler.py`: traceable sampling.
35
+ - `starVLA/scoring/flow_residual.py`: residual scoring.
36
+ - `starVLA/scoring/residual_cache.py`: score-cache handling.
37
+ - `scripts/run_florence_bridge_arm.sh`: portable two-GPU continuation launcher.
38
+ - `scripts/evaluate_bridge_384_no_video.sh`: locked 384-rollout orchestrator.
39
+
40
+ The public dataset and base Florence model are not redistributed in this package. Their local paths are supplied through environment variables.
configs/deepspeed_florence_2gpu.yaml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ compute_environment: LOCAL_MACHINE
2
+ debug: false
3
+ deepspeed_config:
4
+ deepspeed_config_file: configs/ds_florence_zero2.json
5
+ distributed_type: DEEPSPEED
6
+ downcast_bf16: "no"
7
+ enable_cpu_affinity: false
8
+ machine_rank: 0
9
+ main_training_function: main
10
+ mixed_precision: bf16
11
+ num_machines: 1
12
+ num_processes: 2
13
+ rdzv_backend: static
14
+ same_network: true
15
+ use_cpu: false
configs/ds_florence_zero2.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "fp16": {"enabled": false},
3
+ "bf16": {"enabled": true},
4
+ "train_micro_batch_size_per_gpu": "auto",
5
+ "train_batch_size": "auto",
6
+ "gradient_accumulation_steps": "auto",
7
+ "zero_optimization": {
8
+ "stage": 2,
9
+ "allgather_partitions": true,
10
+ "allgather_bucket_size": 50000000,
11
+ "reduce_scatter": true,
12
+ "reduce_bucket_size": 50000000,
13
+ "overlap_comm": true,
14
+ "contiguous_gradients": true,
15
+ "cpu_offload": false
16
+ },
17
+ "gradient_clipping": 1.0,
18
+ "steps_per_print": 10
19
+ }
configs/florence_bridge_20k_plus500.yaml ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: florence-bridge-20k-plus500
2
+ run_root_dir: ${oc.env:OPTR_RUN_ROOT}
3
+ seed: 117465
4
+ wandb_entity: null
5
+ wandb_project: OPTR-Florence-Bridge
6
+ is_debug: false
7
+ version_id: "0.21"
8
+
9
+ framework:
10
+ name: QwenGR00T
11
+ qwenvl:
12
+ base_vlm: ${oc.env:FLORENCE_MODEL_PATH}
13
+ attn_implementation: flash_attention_2
14
+ vl_hidden_dim: 2048
15
+ dino:
16
+ dino_backbone: dinov2_vits14
17
+ action_model:
18
+ action_model_type: DiT-B
19
+ action_hidden_dim: 1024
20
+ hidden_size: 1024
21
+ add_pos_embed: true
22
+ max_seq_len: 1024
23
+ action_dim: 7
24
+ state_dim: 7
25
+ action_horizon: 16
26
+ future_action_window_size: 15
27
+ past_action_window_size: 0
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
+
45
+ datasets:
46
+ vla_data:
47
+ dataset_py: lerobot_datasets
48
+ data_root_dir: ${oc.env:BRIDGE_DATA_ROOT}
49
+ data_mix: bridge_rt_infoflow
50
+ action_type: delta_ee
51
+ action_mode: abs
52
+ precomputed_statistics_path: ${oc.env:BRIDGE_STATISTICS_PATH}
53
+ precomputed_statistics_key_map:
54
+ bridge_orig_lerobot: oxe_bridge
55
+ fractal20220817_data_lerobot: oxe_rt1
56
+ CoT_prompt: Your task is {instruction}. To identify the key objects for your task. Locate their bounding boxes in [x1,y1,x2,y2] format.
57
+ per_device_batch_size: 4
58
+ load_all_data_for_training: true
59
+ obs_image_size: [224, 224]
60
+ video_backend: torchvision_av
61
+ num_workers: 4
62
+ balance_dataset_weights: false
63
+ balance_trajectory_weights: false
64
+ traceable_sampling: true
65
+ expected_world_size: 2
66
+ dataset_provenance_path: ${oc.env:BRIDGE_PROVENANCE_PATH}
67
+ dataset_content_fingerprint_path: ${oc.env:BRIDGE_FINGERPRINT_PATH}
68
+ valid_anchor_manifest_path: ${oc.env:BRIDGE_MANIFEST_PATH}
69
+ temporal_proposal_path: null
70
+ transform_rng_seed: 298226
71
+
72
+ trainer:
73
+ use_deepspeed: true
74
+ max_train_steps: 100000
75
+ num_warmup_steps: 5000
76
+ save_interval: null
77
+ save_steps: [20500]
78
+ evaluation_snapshot_interval: null
79
+ eval_interval: 100001
80
+ repeated_diffusion_steps: 4
81
+ pretrained_checkpoint: null
82
+ is_resume: true
83
+ allow_model_only_resume: false
84
+ learning_rate:
85
+ base: 3.0e-05
86
+ qwen_vl_interface: 1.0e-05
87
+ action_model: 1.0e-04
88
+ lr_scheduler_type: cosine_with_min_lr
89
+ scheduler_specific_kwargs:
90
+ min_lr: 5.0e-07
91
+ freeze_modules: ""
92
+ trainable_modules: ""
93
+ frozen_modules_eval: false
94
+ logging_frequency: 10
95
+ gradient_clipping: 1.0
96
+ gradient_accumulation_steps: 8
97
+ save_format: pt
98
+ save_final_checkpoint: false
99
+ optimizer:
100
+ name: AdamW
101
+ betas: [0.9, 0.95]
102
+ eps: 1.0e-08
103
+ weight_decay: 1.0e-08
104
+ resume_accelerate_checkpoint: ${oc.env:COMMON_START_20K}
105
+ branch_parent_seed: 42
106
+ branch_rng_seed: 117465
107
+ model_rng_seed: 588643
108
+ stop_after_steps: 20500
109
+
110
+ output_dir: ${oc.env:OPTR_RUN_ROOT}/florence-bridge-20k-plus500
configs/reference/common_start_20k_full.yaml ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: florence-scratch-uniform-seed-42-official100k-mb4-eval1k-20260726
2
+ run_root_dir: /mnt/miaochengyu/starvla-infoflow-runtime/runs/florence-scratch-official-100k
3
+ seed: 42
4
+ wandb_entity: null
5
+ wandb_project: InfoFlow-StarVLA-BridgeRT
6
+ is_debug: false
7
+ version_id: '0.21'
8
+ framework:
9
+ name: QwenGR00T
10
+ qwenvl:
11
+ base_vlm: /mnt/miaochengyu/starvla-infoflow-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: /mnt/miaochengyu/starvla-infoflow-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: /mnt/miaochengyu/starvla-infoflow-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/miaochengyu/starVLA-infoflow-codex/protocols/bridgert/data_provenance.json
68
+ dataset_content_fingerprint_path: /mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/content_fingerprints.json
69
+ valid_anchor_manifest_path: /mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/valid_anchor_manifest_florence_scratch_v1.json
70
+ temporal_proposal_path: null
71
+ trainer:
72
+ use_deepspeed: true
73
+ max_train_steps: 100000
74
+ num_warmup_steps: 5000
75
+ save_interval: null
76
+ save_steps:
77
+ - 5000
78
+ - 10000
79
+ - 20000
80
+ - 30000
81
+ - 50000
82
+ - 75000
83
+ - 100000
84
+ evaluation_snapshot_interval: 1000
85
+ eval_interval: 100001
86
+ repeated_diffusion_steps: 4
87
+ pretrained_checkpoint: null
88
+ is_resume: false
89
+ allow_model_only_resume: false
90
+ learning_rate:
91
+ base: 3.0e-05
92
+ qwen_vl_interface: 1.0e-05
93
+ action_model: 0.0001
94
+ lr_scheduler_type: cosine_with_min_lr
95
+ scheduler_specific_kwargs:
96
+ min_lr: 5.0e-07
97
+ freeze_modules: ''
98
+ trainable_modules: ''
99
+ frozen_modules_eval: false
100
+ logging_frequency: 50
101
+ gradient_clipping: 1.0
102
+ gradient_accumulation_steps: 8
103
+ save_format: pt
104
+ save_final_checkpoint: false
105
+ optimizer:
106
+ name: AdamW
107
+ betas:
108
+ - 0.9
109
+ - 0.95
110
+ eps: 1.0e-08
111
+ weight_decay: 1.0e-08
112
+ config_yaml: examples/InfoFlow/train_files/server/bridgert_florence_scratch_official_100k_mb4_eval1k.yaml
113
+ output_dir: /mnt/miaochengyu/starvla-infoflow-runtime/runs/florence-scratch-official-100k/florence-scratch-uniform-seed-42-official100k-mb4-eval1k-20260726
configs/reference/residual_seed117465_full.yaml ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: infoflow-confirmatory-residual-seed-117465
2
+ run_root_dir: /mnt/miaochengyu/starvla-infoflow-runtime/runs/infoflow-confirmatory-22k-20260730
3
+ seed: 117465
4
+ wandb_entity: null
5
+ wandb_project: InfoFlow-StarVLA-BridgeRT
6
+ is_debug: false
7
+ version_id: '0.21'
8
+ framework:
9
+ name: QwenGR00T
10
+ qwenvl:
11
+ base_vlm: /mnt/miaochengyu/starvla-infoflow-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: /mnt/miaochengyu/starvla-infoflow-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: /mnt/miaochengyu/starvla-infoflow-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/miaochengyu/starVLA-infoflow-codex/protocols/bridgert/data_provenance.json
68
+ dataset_content_fingerprint_path: /mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/content_fingerprints.json
69
+ valid_anchor_manifest_path: /mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/valid_anchor_manifest_florence_scratch_v1.json
70
+ valid_anchor_manifest_migration_path: /home/miaochengyu/starVLA-infoflow-codex/protocols/bridgert/valid_anchor_manifest_rng_migration_20260729.json
71
+ temporal_proposal_path: /mnt/miaochengyu/starvla-infoflow-runtime/runs/infoflow-proposals/scratch20k_residual_grid8_gate42f_batch1_20260729/residual_temporal_proposal.json
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
+ - 21000
81
+ - 22000
82
+ evaluation_snapshot_interval: 1000
83
+ eval_interval: 100001
84
+ repeated_diffusion_steps: 4
85
+ pretrained_checkpoint: null
86
+ is_resume: true
87
+ allow_model_only_resume: false
88
+ learning_rate:
89
+ base: 3.0e-05
90
+ qwen_vl_interface: 1.0e-05
91
+ action_model: 0.0001
92
+ lr_scheduler_type: cosine_with_min_lr
93
+ scheduler_specific_kwargs:
94
+ min_lr: 5.0e-07
95
+ freeze_modules: ''
96
+ trainable_modules: ''
97
+ frozen_modules_eval: false
98
+ logging_frequency: 50
99
+ gradient_clipping: 1.0
100
+ gradient_accumulation_steps: 8
101
+ save_format: pt
102
+ save_final_checkpoint: false
103
+ optimizer:
104
+ name: AdamW
105
+ betas:
106
+ - 0.9
107
+ - 0.95
108
+ eps: 1.0e-08
109
+ weight_decay: 1.0e-08
110
+ resume_accelerate_checkpoint: /mnt/miaochengyu/starvla-infoflow-runtime/runs/florence-scratch-official-100k/florence-scratch-uniform-seed-42-official100k-mb4-eval1k-20260726/checkpoints/steps_20000_accelerate
111
+ resume_checkpoint_integrity_manifest: /home/miaochengyu/starVLA-infoflow-codex/protocols/bridgert/common_start_accelerate_integrity_20260730.json
112
+ resume_checkpoint_integrity_manifest_sha256: 899d2cef3f73191e11f7a1628afc05b60c80af5816b8fd77c1c69e90d5c9ff39
113
+ branch_parent_seed: 42
114
+ branch_rng_seed: 117465
115
+ model_rng_seed: 588643
116
+ stop_after_steps: 22000
117
+ config_yaml: /home/miaochengyu/starVLA-infoflow-codex/examples/InfoFlow/train_files/server/generated/infoflow_confirmatory_20k_20260730/confirmatory_residual_seed_117465.yaml
118
+ output_dir: /mnt/miaochengyu/starvla-infoflow-runtime/runs/infoflow-confirmatory-22k-20260730/infoflow-confirmatory-residual-seed-117465
configs/reference/uniform_seed117465_full.yaml ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run_id: infoflow-confirmatory-uniform-seed-117465
2
+ run_root_dir: /mnt/miaochengyu/starvla-infoflow-runtime/runs/infoflow-confirmatory-22k-20260730
3
+ seed: 117465
4
+ wandb_entity: null
5
+ wandb_project: InfoFlow-StarVLA-BridgeRT
6
+ is_debug: false
7
+ version_id: '0.21'
8
+ framework:
9
+ name: QwenGR00T
10
+ qwenvl:
11
+ base_vlm: /mnt/miaochengyu/starvla-infoflow-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: /mnt/miaochengyu/starvla-infoflow-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: /mnt/miaochengyu/starvla-infoflow-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/miaochengyu/starVLA-infoflow-codex/protocols/bridgert/data_provenance.json
68
+ dataset_content_fingerprint_path: /mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/content_fingerprints.json
69
+ valid_anchor_manifest_path: /mnt/miaochengyu/starvla-infoflow-runtime/manifests/bridgert/valid_anchor_manifest_florence_scratch_v1.json
70
+ valid_anchor_manifest_migration_path: /home/miaochengyu/starVLA-infoflow-codex/protocols/bridgert/valid_anchor_manifest_rng_migration_20260729.json
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
+ - 21000
81
+ - 22000
82
+ evaluation_snapshot_interval: 1000
83
+ eval_interval: 100001
84
+ repeated_diffusion_steps: 4
85
+ pretrained_checkpoint: null
86
+ is_resume: true
87
+ allow_model_only_resume: false
88
+ learning_rate:
89
+ base: 3.0e-05
90
+ qwen_vl_interface: 1.0e-05
91
+ action_model: 0.0001
92
+ lr_scheduler_type: cosine_with_min_lr
93
+ scheduler_specific_kwargs:
94
+ min_lr: 5.0e-07
95
+ freeze_modules: ''
96
+ trainable_modules: ''
97
+ frozen_modules_eval: false
98
+ logging_frequency: 50
99
+ gradient_clipping: 1.0
100
+ gradient_accumulation_steps: 8
101
+ save_format: pt
102
+ save_final_checkpoint: false
103
+ optimizer:
104
+ name: AdamW
105
+ betas:
106
+ - 0.9
107
+ - 0.95
108
+ eps: 1.0e-08
109
+ weight_decay: 1.0e-08
110
+ resume_accelerate_checkpoint: /mnt/miaochengyu/starvla-infoflow-runtime/runs/florence-scratch-official-100k/florence-scratch-uniform-seed-42-official100k-mb4-eval1k-20260726/checkpoints/steps_20000_accelerate
111
+ resume_checkpoint_integrity_manifest: /home/miaochengyu/starVLA-infoflow-codex/protocols/bridgert/common_start_accelerate_integrity_20260730.json
112
+ resume_checkpoint_integrity_manifest_sha256: 899d2cef3f73191e11f7a1628afc05b60c80af5816b8fd77c1c69e90d5c9ff39
113
+ branch_parent_seed: 42
114
+ branch_rng_seed: 117465
115
+ model_rng_seed: 588643
116
+ stop_after_steps: 22000
117
+ config_yaml: /home/miaochengyu/starVLA-infoflow-codex/examples/InfoFlow/train_files/server/generated/infoflow_confirmatory_20k_20260730/confirmatory_uniform_seed_117465.yaml
118
+ output_dir: /mnt/miaochengyu/starvla-infoflow-runtime/runs/infoflow-confirmatory-22k-20260730/infoflow-confirmatory-uniform-seed-117465
deployment/__init__.py ADDED
File without changes
deployment/model_server/__init__.py ADDED
File without changes
deployment/model_server/policy_norm_processor.py ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 starVLA community. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ """PolicyNormProcessor — reuse the training-time ComposedModalityTransform.
4
+
5
+ This class replaces the hand-rolled un-normalization math that previously
6
+ lived in every eval client. It rebuilds the *exact* ``ComposedModalityTransform``
7
+ used at training time from a checkpoint:
8
+
9
+ 1. Read ``config.yaml`` next to the ckpt → resolve ``data_mix`` →
10
+ look up ``robot_type`` from ``DATASET_NAMED_MIXTURES`` →
11
+ fetch the ``DataConfig`` from ``ROBOT_TYPE_CONFIG_MAP``.
12
+ 2. Build the transform pipeline via ``data_config.transform()``.
13
+ 3. Reconstruct a ``DatasetMetadata`` from the saved
14
+ ``dataset_statistics.json`` (which stores **combined** per-modality
15
+ arrays of length ``D``) by splitting it into per-key entries that match
16
+ ``data_config.action_keys`` / ``state_keys``.
17
+ 4. ``set_metadata(...)`` binds the metadata into every transform.
18
+
19
+ After construction the caller simply invokes :meth:`unapply_actions` /
20
+ :meth:`apply_state` to get/normalize tensors using the same code path as
21
+ training — there is no second source of truth for normalization math.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ from typing import Any, Dict, List, Optional, Sequence
28
+
29
+ import numpy as np
30
+ import torch
31
+
32
+ from starVLA.dataloader.gr00t_lerobot.registry import (
33
+ DATASET_NAMED_MIXTURES,
34
+ ROBOT_TYPE_CONFIG_MAP,
35
+ )
36
+ from starVLA.dataloader.gr00t_lerobot.schema import (
37
+ DatasetMetadata,
38
+ StateActionMetadata,
39
+ )
40
+ from starVLA.dataloader.gr00t_lerobot.transform.base import ComposedModalityTransform
41
+ from starVLA.model.framework.share_tools import read_mode_config
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+
46
+ def _resolve_robot_type(
47
+ model_cfg: dict,
48
+ unnorm_key: Optional[str] = None,
49
+ ) -> str:
50
+ """Look up the training robot_type from the saved cfg.
51
+
52
+ Convention used by starVLA train scripts:
53
+ ``cfg.datasets.vla_data.data_mix`` is a key in
54
+ ``DATASET_NAMED_MIXTURES`` whose value is a list of
55
+ ``(dataset_name, weight, robot_type)`` tuples.
56
+
57
+ When a data_mix contains entries from multiple robot types (e.g.
58
+ ``bridge_rt_1`` covers ``oxe_bridge`` + ``oxe_rt1``), ``unnorm_key``
59
+ is used to identify which embodiment is requested. In those mixtures
60
+ the ``robot_type`` field of each entry **matches** the top-level key in
61
+ ``dataset_statistics.json``, so ``unnorm_key`` serves as the selector.
62
+ """
63
+ try:
64
+ data_mix = model_cfg["datasets"]["vla_data"]["data_mix"]
65
+ except (KeyError, TypeError) as e:
66
+ raise KeyError(
67
+ "ckpt config.yaml is missing `datasets.vla_data.data_mix`; "
68
+ "cannot resolve training-time robot_type."
69
+ ) from e
70
+
71
+ if data_mix not in DATASET_NAMED_MIXTURES:
72
+ raise KeyError(
73
+ f"data_mix={data_mix!r} not in DATASET_NAMED_MIXTURES "
74
+ f"(available: {sorted(DATASET_NAMED_MIXTURES.keys())[:20]} ...). "
75
+ "Did you forget to register the example under examples/<bench>/train_files/data_registry/?"
76
+ )
77
+
78
+ mixture = DATASET_NAMED_MIXTURES[data_mix]
79
+ robot_types = sorted({entry[2] for entry in mixture})
80
+
81
+ if len(robot_types) == 1:
82
+ return robot_types[0]
83
+
84
+ # Multiple robot types in the mixture.
85
+ # Use unnorm_key as a direct selector: for multi-robot mixtures the
86
+ # dataset_statistics.json top-level keys equal the robot_type values.
87
+ if unnorm_key is not None and unnorm_key in robot_types:
88
+ return unnorm_key
89
+
90
+ raise ValueError(
91
+ f"data_mix={data_mix!r} contains multiple robot_types {robot_types}. "
92
+ "Pass `unnorm_key` matching one of them to disambiguate "
93
+ f"(e.g. unnorm_key={robot_types[0]!r})."
94
+ )
95
+
96
+
97
+ def _infer_key_dims(
98
+ data_config: Any,
99
+ combined_stats: Dict[str, Any],
100
+ modality_keys: Sequence[str],
101
+ modality: str,
102
+ ) -> Dict[str, int]:
103
+ """Compute per-key dimensions for splitting combined stats arrays.
104
+
105
+ Lookup priority:
106
+ 1. ``data_config.<modality>_key_dims`` — explicit dict classvar on the DataConfig
107
+ (required for DataConfigs with non-uniform or multi-dim keys).
108
+ 2. Infer uniformly from stats array length: if ``D_total / n_keys`` is an
109
+ integer, use that as the uniform per-key dim.
110
+ 3. Fall back to dim=1 when no stats are available (empty combined dict).
111
+
112
+ Args:
113
+ data_config: The DataConfig instance for the current robot type.
114
+ combined_stats: The raw stats dict for the chosen unnorm_key (the value
115
+ of ``norm_stats[unnorm_key]`` from ``dataset_statistics.json``).
116
+ modality_keys: Ordered list of full keys for this modality
117
+ (e.g. ``["action.left_joints", ...]``).
118
+ modality: ``"action"`` or ``"state"``.
119
+
120
+ Returns:
121
+ Dict mapping each full key to its integer dimension.
122
+ """
123
+ attr = f"{modality}_key_dims"
124
+ if hasattr(data_config, attr):
125
+ return dict(getattr(data_config, attr))
126
+
127
+ combined = combined_stats.get(modality, {})
128
+ stat_arr = next((v for k, v in combined.items() if k != "mask"), None)
129
+ n_keys = len(modality_keys)
130
+ if stat_arr is not None and n_keys > 0:
131
+ D_total = len(stat_arr)
132
+ if D_total == n_keys:
133
+ return {k: 1 for k in modality_keys}
134
+ elif n_keys > 0 and D_total % n_keys == 0:
135
+ dim = D_total // n_keys
136
+ return {k: dim for k in modality_keys}
137
+ else:
138
+ raise ValueError(
139
+ f"Cannot infer per-key dims for modality={modality!r}: "
140
+ f"D_total={D_total} is not evenly divisible by n_keys={n_keys}. "
141
+ f"Add `{attr} = {{key: dim, ...}}` to the DataConfig "
142
+ f"(keys={list(modality_keys)})."
143
+ )
144
+ return {k: 1 for k in modality_keys}
145
+
146
+
147
+ def _build_dataset_metadata(
148
+ stats_for_key: Dict[str, Any],
149
+ embodiment_tag: Any,
150
+ action_keys: Sequence[str],
151
+ state_keys: Sequence[str],
152
+ action_key_dims: Optional[Dict[str, int]] = None,
153
+ state_key_dims: Optional[Dict[str, int]] = None,
154
+ ) -> DatasetMetadata:
155
+ """Convert the *combined* stats arrays from ``dataset_statistics.json``
156
+ back into a per-subkey :class:`DatasetMetadata` matching what the training
157
+ pipeline produced.
158
+
159
+ The saved ``dataset_statistics.json`` stores stats as flat arrays of
160
+ length ``D = sum(per_key_dims)``. For each ``"action.<sub>"`` key we
161
+ slice out ``dim_k`` elements starting at the current cursor and store them
162
+ under ``statistics.action.<sub> = {"min": [v0..v_{k-1}], ...}``.
163
+
164
+ Args:
165
+ stats_for_key: ``norm_stats[unnorm_key]`` from ``dataset_statistics.json``.
166
+ embodiment_tag: Embodiment tag from the DataConfig.
167
+ action_keys: Ordered list of full action keys.
168
+ state_keys: Ordered list of full state keys.
169
+ action_key_dims: Per-key dimension dict (``{full_key: dim_k}``).
170
+ Defaults to dim=1 for every key.
171
+ state_key_dims: Per-key dimension dict for state keys.
172
+ Defaults to dim=1 for every key.
173
+ """
174
+ if action_key_dims is None:
175
+ action_key_dims = {k: 1 for k in action_keys}
176
+ if state_key_dims is None:
177
+ state_key_dims = {k: 1 for k in state_keys}
178
+
179
+ def _split_combined(
180
+ combined: Dict[str, Sequence[float]],
181
+ keys: Sequence[str],
182
+ key_dims: Dict[str, int],
183
+ ):
184
+ """Split combined arrays into per-subkey dicts using per-key dims.
185
+
186
+ ``combined`` looks like ``{"min": [..D..], "max": [..D..], "mask": [..D..], ...}``.
187
+ ``keys`` is the ordered list of full keys.
188
+ ``key_dims`` maps each full key to its integer dimension.
189
+ Returns ``(stats_per_subkey, meta_per_subkey)``.
190
+ """
191
+ stats_per_subkey: Dict[str, Dict[str, List[float]]] = {}
192
+ meta_per_subkey: Dict[str, StateActionMetadata] = {}
193
+ cursor = 0
194
+ for full_key in keys:
195
+ subkey = full_key.split(".", 1)[1]
196
+ dim_k = key_dims.get(full_key, 1)
197
+ per_key: Dict[str, List[float]] = {}
198
+ for stat_name, arr in combined.items():
199
+ if stat_name == "mask":
200
+ continue
201
+ end = cursor + dim_k
202
+ if end > len(arr):
203
+ # Saved combined array shorter than expected (truncated
204
+ # pad channels etc.). Skip this stat field silently.
205
+ continue
206
+ per_key[stat_name] = [float(v) for v in arr[cursor:end]]
207
+ stats_per_subkey[subkey] = per_key
208
+ meta_per_subkey[subkey] = StateActionMetadata(
209
+ absolute=True,
210
+ rotation_type=None,
211
+ shape=(dim_k,),
212
+ continuous=True,
213
+ )
214
+ cursor += dim_k
215
+ return stats_per_subkey, meta_per_subkey
216
+
217
+ action_combined = stats_for_key.get("action", {})
218
+ state_combined = stats_for_key.get("state", {})
219
+
220
+ action_stats, action_meta = _split_combined(action_combined, action_keys, action_key_dims)
221
+ state_stats, state_meta = _split_combined(state_combined, state_keys, state_key_dims)
222
+
223
+ # Pydantic accepts dict input with field validators
224
+ return DatasetMetadata.model_validate(
225
+ {
226
+ "statistics": {
227
+ "state": state_stats,
228
+ "action": action_stats,
229
+ },
230
+ "modalities": {
231
+ "video": {},
232
+ "state": state_meta,
233
+ "action": action_meta,
234
+ },
235
+ "embodiment_tag": embodiment_tag.value
236
+ if hasattr(embodiment_tag, "value")
237
+ else embodiment_tag,
238
+ }
239
+ )
240
+
241
+
242
+ class PolicyNormProcessor:
243
+ """Server-side normalization helper backed by training-time transforms.
244
+
245
+ Construct once per checkpoint; call :meth:`unapply_actions` to convert
246
+ a normalized action chunk ``(T, D)`` back to env-space.
247
+
248
+ Args:
249
+ ckpt_path: Path to the ``*.pt`` checkpoint (the loader looks for
250
+ ``config.yaml`` and ``dataset_statistics.json`` two dirs up).
251
+ unnorm_key: Which top-level key in ``dataset_statistics.json`` to use.
252
+ ``None`` → auto-pick the only key.
253
+ """
254
+
255
+ def __init__(self, ckpt_path: str, unnorm_key: Optional[str] = None) -> None:
256
+ self._ckpt_path = str(ckpt_path)
257
+ cfg, norm_stats = read_mode_config(self._ckpt_path)
258
+ self._model_cfg = cfg
259
+ self._norm_stats = norm_stats
260
+
261
+ # 3-early) Pick the requested unnorm_key (or auto-select) BEFORE
262
+ # resolving robot_type so we can use it as a hint for multi-robot mixtures.
263
+ if unnorm_key is None:
264
+ if len(norm_stats) == 1:
265
+ unnorm_key = next(iter(norm_stats.keys()))
266
+ # else: defer error to step 3 below after robot_type resolution attempt
267
+ elif unnorm_key not in norm_stats:
268
+ raise KeyError(
269
+ f"unnorm_key={unnorm_key!r} not in {list(norm_stats.keys())}"
270
+ )
271
+ self._unnorm_key = unnorm_key # may still be None for multi-key case
272
+
273
+ # 1) Resolve which DataConfig was used at training.
274
+ robot_type = _resolve_robot_type(cfg, unnorm_key=unnorm_key)
275
+ if robot_type not in ROBOT_TYPE_CONFIG_MAP:
276
+ raise KeyError(
277
+ f"robot_type={robot_type!r} not in ROBOT_TYPE_CONFIG_MAP "
278
+ f"(available: {sorted(ROBOT_TYPE_CONFIG_MAP.keys())}). "
279
+ "Make sure the example's data_registry/data_config.py is importable."
280
+ )
281
+ self._data_config = ROBOT_TYPE_CONFIG_MAP[robot_type]
282
+ self._action_keys: List[str] = list(self._data_config.action_keys)
283
+ self._state_keys: List[str] = list(getattr(self._data_config, "state_keys", []))
284
+
285
+ # 2) Build training-time transform pipeline.
286
+ transform = self._data_config.transform()
287
+ if not isinstance(transform, ComposedModalityTransform):
288
+ transform = ComposedModalityTransform(transforms=[transform])
289
+ self._transform = transform
290
+
291
+ # 3) Pick the requested unnorm_key (finalize; error if still None here).
292
+ if self._unnorm_key is None:
293
+ raise ValueError(
294
+ f"Multiple unnorm_keys in dataset_statistics.json: "
295
+ f"{list(norm_stats.keys())}. Pass unnorm_key explicitly."
296
+ )
297
+ unnorm_key = self._unnorm_key
298
+
299
+ # 4) Resolve per-key dims (handles multi-d action/state keys).
300
+ stats_for_unnorm = norm_stats[unnorm_key]
301
+ self._action_key_dims: Dict[str, int] = _infer_key_dims(
302
+ self._data_config, stats_for_unnorm, self._action_keys, "action"
303
+ )
304
+ self._state_key_dims: Dict[str, int] = _infer_key_dims(
305
+ self._data_config, stats_for_unnorm, self._state_keys, "state"
306
+ )
307
+
308
+ # 5) Build & bind metadata.
309
+ ds_meta = _build_dataset_metadata(
310
+ stats_for_key=stats_for_unnorm,
311
+ embodiment_tag=self._data_config.embodiment_tag,
312
+ action_keys=self._action_keys,
313
+ state_keys=self._state_keys,
314
+ action_key_dims=self._action_key_dims,
315
+ state_key_dims=self._state_key_dims,
316
+ )
317
+ self._transform.set_metadata(ds_meta)
318
+ self._transform.eval() # mark transforms as eval-mode
319
+
320
+ logger.info(
321
+ "PolicyNormProcessor ready: robot_type=%s, unnorm_key=%s, "
322
+ "action_keys=%s (dims=%s), state_keys=%s",
323
+ robot_type,
324
+ unnorm_key,
325
+ self._action_keys,
326
+ [self._action_key_dims[k] for k in self._action_keys],
327
+ self._state_keys,
328
+ )
329
+
330
+ # ------------------------------------------------------------------
331
+ # Public properties
332
+ # ------------------------------------------------------------------
333
+ @property
334
+ def action_keys(self) -> List[str]:
335
+ return list(self._action_keys)
336
+
337
+ @property
338
+ def state_keys(self) -> List[str]:
339
+ return list(self._state_keys)
340
+
341
+ @property
342
+ def unnorm_key(self) -> str:
343
+ return self._unnorm_key
344
+
345
+ @property
346
+ def available_unnorm_keys(self) -> List[str]:
347
+ return list(self._norm_stats.keys())
348
+
349
+ @property
350
+ def transform(self) -> ComposedModalityTransform:
351
+ return self._transform
352
+
353
+ # ------------------------------------------------------------------
354
+ # Inverse path (model output → env action)
355
+ # ------------------------------------------------------------------
356
+ def unapply_actions(self, normalized_actions: np.ndarray) -> np.ndarray:
357
+ """Invert action normalization using the training-time pipeline.
358
+
359
+ Args:
360
+ normalized_actions: shape ``(T, D)`` where
361
+ ``D == sum(action_key_dims.values())``.
362
+
363
+ Returns:
364
+ ``(T, D)`` un-normalized actions in env coordinates.
365
+ """
366
+ normalized_actions = np.asarray(normalized_actions)
367
+ assert normalized_actions.ndim == 2, (
368
+ f"Expected (T, D); got shape {normalized_actions.shape}"
369
+ )
370
+
371
+ # Split (T, D) into per-key {full_key: torch.Tensor[T, dim_k]}.
372
+ data: Dict[str, torch.Tensor] = {}
373
+ cursor = 0
374
+ for full_key in self._action_keys:
375
+ dim_k = self._action_key_dims.get(full_key, 1)
376
+ slice_ = normalized_actions[..., cursor : cursor + dim_k]
377
+ data[full_key] = torch.as_tensor(slice_, dtype=torch.float32)
378
+ cursor += dim_k
379
+
380
+ if cursor != normalized_actions.shape[-1]:
381
+ raise ValueError(
382
+ f"Sum of per-key dims ({cursor}) != action_dim "
383
+ f"({normalized_actions.shape[-1]}). "
384
+ f"action_keys={self._action_keys}, "
385
+ f"action_key_dims={self._action_key_dims}"
386
+ )
387
+
388
+ out = self._transform.unapply(data)
389
+
390
+ parts: List[np.ndarray] = []
391
+ for full_key in self._action_keys:
392
+ v = out[full_key]
393
+ if isinstance(v, torch.Tensor):
394
+ v = v.detach().cpu().numpy()
395
+ parts.append(np.asarray(v))
396
+ return np.concatenate(parts, axis=-1)
deployment/model_server/policy_wrapper.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 starVLA community. All rights reserved.
2
+ # Licensed under the MIT License.
3
+ """Policy server wrapper.
4
+
5
+ Encapsulates a `baseframework` instance plus a :class:`PolicyNormProcessor`
6
+ that reuses the *training-time* :class:`ComposedModalityTransform` for action
7
+ un-normalization (no hand-rolled math). The websocket server returns
8
+ already-unnormalized actions.
9
+
10
+ Client-side responsibilities that REMAIN on the client:
11
+ - environment-specific adapters (image_history, gripper sticky, action
12
+ ensembling)
13
+ - chunk-cache scheduling (`step % chunk_size == 0` triggers a new infer)
14
+
15
+ Exposed API:
16
+ - ``metadata`` (dict, sent at handshake): ``action_chunk_size``,
17
+ ``available_unnorm_keys``, ``action_keys``, ``state_keys``.
18
+ - ``predict_action(examples, unnorm_key=None, **kwargs)`` returns
19
+ ``{"actions": np.ndarray[B, T, action_dim]}``.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import logging
25
+ from typing import Any, Dict, List, Optional
26
+
27
+ import numpy as np
28
+ import torch
29
+
30
+ from starVLA.model.framework.base_framework import baseframework
31
+ from starVLA.model.framework.share_tools import read_mode_config
32
+
33
+ from deployment.model_server.policy_norm_processor import PolicyNormProcessor
34
+
35
+
36
+ class PolicyServerWrapper:
37
+ """Wraps a `baseframework` for use as a websocket-server policy."""
38
+
39
+ def __init__(
40
+ self,
41
+ ckpt_path: str,
42
+ device: str = "cuda",
43
+ use_bf16: bool = False,
44
+ unnorm_key: Optional[str] = None,
45
+ normalized_action_clip: Optional[float] = None,
46
+ ) -> None:
47
+ self._ckpt_path = str(ckpt_path)
48
+ self._normalized_action_clip = normalized_action_clip
49
+
50
+ logging.info("PolicyServerWrapper: loading framework from %s", self._ckpt_path)
51
+ framework = baseframework.from_pretrained(self._ckpt_path)
52
+ if use_bf16:
53
+ framework = framework.to(torch.bfloat16)
54
+ framework = framework.to(device).eval()
55
+ self._framework = framework
56
+
57
+ # Co-located metadata.
58
+ model_cfg, _ = read_mode_config(self._ckpt_path)
59
+ self._model_cfg = model_cfg
60
+ vla_data_cfg = model_cfg.get("datasets", {}).get("vla_data", {})
61
+ include_state = vla_data_cfg.get("include_state", False)
62
+ self._uses_state = include_state not in (False, None, 0, "False", "false", "0")
63
+
64
+ # action_chunk_size = future_action_window_size + 1 (matches old client).
65
+ action_model_cfg = model_cfg["framework"]["action_model"]
66
+
67
+ if "action_horizon" in action_model_cfg:
68
+ self._action_chunk_size = int(action_model_cfg["action_horizon"])
69
+ elif "future_action_window_size" in action_model_cfg:
70
+ self._action_chunk_size = int(action_model_cfg["future_action_window_size"]) + 1
71
+ else:
72
+ raise ValueError(
73
+ f"PolicyServerWrapper: no action_horizon or future_action_window_size found in model config for {self._ckpt_path}"
74
+ )
75
+ # Cache of PolicyNormProcessor instances per unnorm_key.
76
+ # For single-dataset ckpts unnorm_key is auto-selected; for multi-dataset
77
+ # ckpts clients must pass unnorm_key per request.
78
+ self._default_unnorm_key = unnorm_key
79
+ self._norm_processors: Dict[str, PolicyNormProcessor] = {}
80
+
81
+ # Peek at available keys without building a full processor.
82
+ _, _ns = read_mode_config(self._ckpt_path)
83
+ self._available_unnorm_keys: List[str] = list(_ns.keys())
84
+
85
+ # Eagerly build when unambiguous; defer for multi-key / no explicit key.
86
+ if unnorm_key is not None or len(self._available_unnorm_keys) == 1:
87
+ default_proc = self._get_processor(unnorm_key)
88
+ self._default_unnorm_key = default_proc.unnorm_key
89
+ logging.info(
90
+ "PolicyServerWrapper ready: action_chunk_size=%d, default_unnorm_key=%s, "
91
+ "available_unnorm_keys=%s, action_keys=%s, state_keys=%s",
92
+ self._action_chunk_size,
93
+ default_proc.unnorm_key,
94
+ default_proc.available_unnorm_keys,
95
+ default_proc.action_keys,
96
+ default_proc.state_keys,
97
+ )
98
+ else:
99
+ logging.info(
100
+ "PolicyServerWrapper ready (multi-key): action_chunk_size=%d, "
101
+ "available_unnorm_keys=%s — clients must pass unnorm_key per request.",
102
+ self._action_chunk_size,
103
+ self._available_unnorm_keys,
104
+ )
105
+
106
+ def _get_processor(self, unnorm_key: Optional[str]) -> PolicyNormProcessor:
107
+ cache_key = unnorm_key if unnorm_key is not None else "__default__"
108
+ if cache_key not in self._norm_processors:
109
+ self._norm_processors[cache_key] = PolicyNormProcessor(
110
+ self._ckpt_path, unnorm_key=unnorm_key
111
+ )
112
+ return self._norm_processors[cache_key]
113
+
114
+ @property
115
+ def metadata(self) -> Dict[str, Any]:
116
+ """Model-invariant metadata; sent to client at websocket handshake."""
117
+ base = {
118
+ "env": "starvla_policy_server",
119
+ "ckpt_path": self._ckpt_path,
120
+ "action_chunk_size": self._action_chunk_size,
121
+ "uses_state": self._uses_state,
122
+ "available_unnorm_keys": self._available_unnorm_keys,
123
+ "default_unnorm_key": self._default_unnorm_key,
124
+ "normalized_action_clip": self._normalized_action_clip,
125
+ "action_head_attention_mask": False,
126
+ "inference_rng": "continuous_server_stream",
127
+ }
128
+ # Enrich with per-embodiment keys when a default processor already exists.
129
+ if self._default_unnorm_key is not None:
130
+ proc = self._get_processor(self._default_unnorm_key)
131
+ base["action_keys"] = proc.action_keys
132
+ base["state_keys"] = proc.state_keys
133
+ return base
134
+
135
+ def predict_action(
136
+ self,
137
+ examples: List[dict],
138
+ unnorm_key: Optional[str] = None,
139
+ **kwargs,
140
+ ) -> Dict[str, np.ndarray]:
141
+ """Run the framework, then un-normalize via training-time transforms.
142
+
143
+ Args:
144
+ examples: list of dicts (each with ``image`` / ``lang`` / optional ``state``).
145
+ unnorm_key: dataset key for un-normalization stats. ``None`` -->
146
+ use the wrapper's default (auto-picked at startup).
147
+ **kwargs: forwarded to the framework's ``predict_action``
148
+ (``do_sample``, ``use_ddim``, ``num_ddim_steps``, ...).
149
+
150
+ Returns:
151
+ ``{"actions": np.ndarray[B, T, D]}`` -- un-normalized.
152
+ """
153
+ effective_key = unnorm_key if unnorm_key is not None else self._default_unnorm_key
154
+ if effective_key is None:
155
+ if len(self._available_unnorm_keys) == 1:
156
+ effective_key = self._available_unnorm_keys[0]
157
+ else:
158
+ raise ValueError(
159
+ f"predict_action: unnorm_key not specified and no default set. "
160
+ f"Pass one of {self._available_unnorm_keys}."
161
+ )
162
+ proc = self._get_processor(effective_key)
163
+
164
+ out = self._framework.predict_action(examples=examples, **kwargs)
165
+ normalized = np.asarray(out["normalized_actions"]) # (B, T, D)
166
+ if self._normalized_action_clip is not None:
167
+ limit = float(self._normalized_action_clip)
168
+ normalized = np.clip(normalized, -limit, limit)
169
+
170
+ unnorm = np.stack(
171
+ [proc.unapply_actions(normalized[b]) for b in range(normalized.shape[0])],
172
+ axis=0,
173
+ )
174
+ return {"actions": unnorm}
deployment/model_server/server_policy.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 starVLA community. All rights reserved.
2
+ # Licensed under the MIT License, Version 1.0 (the "License");
3
+ # Implemented by [Jinhui YE / HKUST University] in [2025].
4
+
5
+ import logging
6
+ import socket
7
+ import argparse
8
+ from deployment.model_server.tools.websocket_policy_server import WebsocketPolicyServer
9
+ from deployment.model_server.policy_wrapper import PolicyServerWrapper
10
+ import os
11
+
12
+
13
+ def main(args) -> None:
14
+ # Example usage:
15
+ # policy = YourPolicyClass() # Replace with your actual policy class
16
+ # server = WebsocketPolicyServer(policy, host="localhost", port=10091)
17
+ # server.serve_forever()
18
+
19
+ policy = PolicyServerWrapper(
20
+ ckpt_path=args.ckpt_path,
21
+ device="cuda",
22
+ use_bf16=args.use_bf16,
23
+ )
24
+
25
+ hostname = socket.gethostname()
26
+ local_ip = socket.gethostbyname(hostname)
27
+ logging.info("Creating server (host: %s, ip: %s)", hostname, local_ip)
28
+
29
+ # start websocket server
30
+ server = WebsocketPolicyServer(
31
+ policy=policy,
32
+ host="0.0.0.0",
33
+ port=args.port,
34
+ idle_timeout=args.idle_timeout,
35
+ metadata={**policy.metadata, "policy_seed": args.seed},
36
+ )
37
+ logging.info("server running ...")
38
+ server.serve_forever()
39
+
40
+
41
+ def build_argparser():
42
+ parser = argparse.ArgumentParser()
43
+ parser.add_argument("--ckpt_path", type=str, default="Qwen/Qwen2.5-VL-3B-Instruct")
44
+ parser.add_argument("--port", type=int, default=10093)
45
+ parser.add_argument("--use_bf16", action="store_true")
46
+ parser.add_argument("--seed", type=int, default=0)
47
+ parser.add_argument("--idle_timeout" , type=int, default=1800, help="Idle timeout in seconds, -1 means never close")
48
+ return parser
49
+
50
+
51
+ def start_debugpy_once():
52
+ """start debugpy once"""
53
+ import debugpy
54
+ if getattr(start_debugpy_once, "_started", False):
55
+ return
56
+ debugpy.listen(("0.0.0.0", 10095))
57
+ print("🔍 Waiting for VSCode attach on 0.0.0.0:10095 ...")
58
+ debugpy.wait_for_client()
59
+ start_debugpy_once._started = True
60
+
61
+
62
+ if __name__ == "__main__":
63
+ logging.basicConfig(level=logging.INFO, force=True)
64
+ parser = build_argparser()
65
+ args = parser.parse_args()
66
+ if os.getenv("DEBUG", False):
67
+ print("🔍 DEBUGPY is enabled")
68
+ start_debugpy_once()
69
+ main(args)
deployment/model_server/tools/__init__.py ADDED
File without changes
deployment/model_server/tools/image_tools.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from PIL import Image
3
+
4
+
5
+ def convert_to_uint8(img: np.ndarray) -> np.ndarray:
6
+ """Converts an image to uint8 if it is a float image.
7
+
8
+ This is important for reducing the size of the image when sending it over the network.
9
+ """
10
+ if np.issubdtype(img.dtype, np.floating):
11
+ img = (255 * img).astype(np.uint8)
12
+ return img
13
+
14
+
15
+ def resize_with_pad(images: np.ndarray, height: int, width: int, method=Image.BILINEAR) -> np.ndarray:
16
+ """Replicates tf.image.resize_with_pad for multiple images using PIL. Resizes a batch of images to a target height.
17
+
18
+ Args:
19
+ images: A batch of images in [..., height, width, channel] format.
20
+ height: The target height of the image.
21
+ width: The target width of the image.
22
+ method: The interpolation method to use. Default is bilinear.
23
+
24
+ Returns:
25
+ The resized images in [..., height, width, channel].
26
+ """
27
+ # If the images are already the correct size, return them as is.
28
+ if images.shape[-3:-1] == (height, width):
29
+ return images
30
+
31
+ original_shape = images.shape
32
+
33
+ images = images.reshape(-1, *original_shape[-3:])
34
+ resized = np.stack([_resize_with_pad_pil(Image.fromarray(im), height, width, method=method) for im in images])
35
+ return resized.reshape(*original_shape[:-3], *resized.shape[-3:])
36
+
37
+
38
+ def _resize_with_pad_pil(image: Image.Image, height: int, width: int, method: int) -> Image.Image:
39
+ """Replicates tf.image.resize_with_pad for one image using PIL. Resizes an image to a target height and
40
+ width without distortion by padding with zeros.
41
+
42
+ Unlike the jax version, note that PIL uses [width, height, channel] ordering instead of [batch, h, w, c].
43
+ """
44
+ cur_width, cur_height = image.size
45
+ if cur_width == width and cur_height == height:
46
+ return image # No need to resize if the image is already the correct size.
47
+
48
+ ratio = max(cur_width / width, cur_height / height)
49
+ resized_height = int(cur_height / ratio)
50
+ resized_width = int(cur_width / ratio)
51
+ resized_image = image.resize((resized_width, resized_height), resample=method)
52
+
53
+ zero_image = Image.new(resized_image.mode, (width, height), 0)
54
+ pad_height = max(0, int((height - resized_height) / 2))
55
+ pad_width = max(0, int((width - resized_width) / 2))
56
+ zero_image.paste(resized_image, (pad_width, pad_height))
57
+ assert zero_image.size == (width, height)
58
+ return zero_image
59
+
60
+
61
+ from typing import Any
62
+
63
+
64
+ def to_pil_preserve(images: Any, scale_float: bool = True):
65
+ """
66
+ Convert (possibly nested) numpy image arrays back to PIL.Image WITHOUT changing spatial shape
67
+ or nesting structure.
68
+
69
+ Accepts:
70
+ - np.ndarray with shape (H, W, C), C in {1,3,4}, dtype uint8 or float
71
+ - PIL.Image.Image (returned as-is)
72
+ - Nested list / tuple structures containing the above
73
+
74
+ Guarantees:
75
+ - No resize / pad / crop performed
76
+ - Returns an object with the SAME nesting layout (list -> list, tuple -> tuple)
77
+ - Only dtype (float -> uint8) and channel-mode adaptation may happen
78
+ * float arrays assumed in [0,1] if scale_float=True (scaled *255 + clip)
79
+ Args:
80
+ images: input object / sequence
81
+ scale_float: whether to scale float images in [0,1] to uint8
82
+ Returns:
83
+ Mirrored structure with all leaf nodes as PIL.Image.Image
84
+ """
85
+
86
+ def _convert(obj):
87
+ # Nested containers
88
+ if isinstance(obj, list):
89
+ return [_convert(x) for x in obj]
90
+ if isinstance(obj, tuple):
91
+ return tuple(_convert(x) for x in obj)
92
+
93
+ # PIL stays
94
+ if isinstance(obj, Image.Image):
95
+ return obj
96
+
97
+ # numpy -> PIL
98
+ if isinstance(obj, np.ndarray):
99
+ arr = obj
100
+ if arr.ndim != 3:
101
+ raise ValueError(f"Expected 3D array (H,W,C), got shape={arr.shape}")
102
+ if arr.shape[2] not in (1, 3, 4):
103
+ raise ValueError(f"Channel count must be 1/3/4, got {arr.shape[2]}")
104
+ if np.issubdtype(arr.dtype, np.floating):
105
+ if scale_float:
106
+ arr = np.clip(arr, 0.0, 1.0)
107
+ arr = (arr * 255.0 + 0.5).astype(np.uint8)
108
+ else:
109
+ raise TypeError("Float array provided but scale_float=False")
110
+ elif arr.dtype != np.uint8:
111
+ arr = arr.astype(np.uint8)
112
+
113
+ # Single channel -> 'L'
114
+ if arr.shape[2] == 1:
115
+ arr = arr[:, :, 0]
116
+ return Image.fromarray(arr, mode="L")
117
+ # 3 channels -> RGB, 4 -> RGBA
118
+ mode = "RGB" if arr.shape[2] == 3 else "RGBA"
119
+ return Image.fromarray(arr, mode=mode)
120
+
121
+ raise TypeError(f"Unsupported element type: {type(obj)}")
122
+
123
+ return _convert(images)
deployment/model_server/tools/msgpack_numpy.py ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Adds NumPy array support to msgpack.
2
+
3
+ msgpack is good for (de)serializing data over a network for multiple reasons:
4
+ - msgpack is secure (as opposed to pickle/dill/etc which allow for arbitrary code execution)
5
+ - msgpack is widely used and has good cross-language support
6
+ - msgpack does not require a schema (as opposed to protobuf/flatbuffers/etc) which is convenient in dynamically typed
7
+ languages like Python and JavaScript
8
+ - msgpack is fast and efficient (as opposed to readable formats like JSON/YAML/etc); I found that msgpack was ~4x faster
9
+ than pickle for serializing large arrays using the below strategy
10
+
11
+ The code below is adapted from https://github.com/lebedov/msgpack-numpy. The reason not to use that library directly is
12
+ that it falls back to pickle for object arrays.
13
+ """
14
+
15
+ import functools
16
+
17
+ import msgpack
18
+ import numpy as np
19
+
20
+
21
+ def pack_array(obj):
22
+ if (isinstance(obj, (np.ndarray, np.generic))) and obj.dtype.kind in ("V", "O", "c"):
23
+ raise ValueError(f"Unsupported dtype: {obj.dtype}")
24
+
25
+ if isinstance(obj, np.ndarray):
26
+ return {
27
+ b"__ndarray__": True,
28
+ b"data": obj.tobytes(),
29
+ b"dtype": obj.dtype.str,
30
+ b"shape": obj.shape,
31
+ }
32
+
33
+ if isinstance(obj, np.generic):
34
+ return {
35
+ b"__npgeneric__": True,
36
+ b"data": obj.item(),
37
+ b"dtype": obj.dtype.str,
38
+ }
39
+
40
+ return obj
41
+
42
+
43
+ def unpack_array(obj):
44
+ if b"__ndarray__" in obj:
45
+ return np.ndarray(buffer=obj[b"data"], dtype=np.dtype(obj[b"dtype"]), shape=obj[b"shape"])
46
+
47
+ if b"__npgeneric__" in obj:
48
+ return np.dtype(obj[b"dtype"]).type(obj[b"data"])
49
+
50
+ return obj
51
+
52
+
53
+ Packer = functools.partial(msgpack.Packer, default=pack_array)
54
+ packb = functools.partial(msgpack.packb, default=pack_array)
55
+
56
+ Unpacker = functools.partial(msgpack.Unpacker, object_hook=unpack_array)
57
+ unpackb = functools.partial(msgpack.unpackb, object_hook=unpack_array)
deployment/model_server/tools/websocket_policy_client.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 starVLA community. All rights reserved.
2
+ # Licensed under the MIT License, Version 1.0 (the "License");
3
+ # Implemented by [Jinhui YE / HKUST University] in [2025].
4
+
5
+ import logging, argparse
6
+ import time, os
7
+ from typing import Dict, Optional, Tuple
8
+
9
+ from typing_extensions import override
10
+ import websockets.sync.client
11
+
12
+ from . import msgpack_numpy
13
+
14
+
15
+ class WebsocketClientPolicy:
16
+ """Implements the Policy interface by communicating with a server over websocket.
17
+
18
+ See WebsocketPolicyServer for a corresponding server implementation.
19
+ """
20
+
21
+ def __init__(self, host: str = "127.0.0.1", port: Optional[int] = 10093, api_key: Optional[str] = None) -> None:
22
+ # 0.0.0.0 cannot be used as a connection target, here default 127.0.0.1
23
+ self._uri = f"ws://{host}"
24
+ if port is not None:
25
+ self._uri += f":{port}"
26
+ self._packer = msgpack_numpy.Packer()
27
+ self._api_key = api_key
28
+ self._ws, self._server_metadata = self._wait_for_server()
29
+
30
+ def get_server_metadata(self) -> Dict:
31
+ return self._server_metadata
32
+
33
+ def _wait_for_server(self, timeout: float = 300) -> Tuple[websockets.sync.client.ClientConnection, Dict]:
34
+ logging.info(f"Waiting for server at {self._uri}...")
35
+ start_time = time.time()
36
+
37
+ for k in ("HTTP_PROXY", "http_proxy", "HTTPS_PROXY", "https_proxy", "ALL_PROXY", "all_proxy"):
38
+ os.environ.pop(k, None)
39
+
40
+ while True:
41
+ if time.time() - start_time > timeout:
42
+ raise TimeoutError(f"Failed to connect to server within {timeout} seconds")
43
+
44
+ try:
45
+ headers = {"Authorization": f"Api-Key {self._api_key}"} if self._api_key else None
46
+ conn = websockets.sync.client.connect(
47
+ self._uri,
48
+ compression=None,
49
+ max_size=None,
50
+ additional_headers=headers,
51
+ open_timeout=150,
52
+ ping_interval=None,
53
+ ping_timeout=None,
54
+ )
55
+ metadata = msgpack_numpy.unpackb(conn.recv())
56
+ return conn, metadata
57
+ except ConnectionRefusedError:
58
+ logging.info(f"Still waiting for server {self._uri} ...")
59
+ time.sleep(2)
60
+
61
+ def close(self) -> None:
62
+ try:
63
+ self._ws.close()
64
+ except Exception:
65
+ pass
66
+
67
+ @override
68
+ def predict_action(self, query_info: Dict) -> Dict:
69
+ data = self._packer.pack(query_info)
70
+ self._ws.send(data)
71
+ response = self._ws.recv()
72
+ if isinstance(response, str):
73
+ raise RuntimeError(f"Error in inference server:\n{response}")
74
+ return msgpack_numpy.unpackb(response)
75
+
76
+
77
+
deployment/model_server/tools/websocket_policy_server.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 starVLA community. All rights reserved.
2
+ # Licensed under the MIT License, Version 1.0 (the "License");
3
+ # Implemented by [Jinhui YE / HKUST University] in [2025].
4
+
5
+ import asyncio
6
+ import logging
7
+ import traceback
8
+ import time
9
+
10
+ import websockets.asyncio.server
11
+ import websockets.frames
12
+
13
+ # from openpi_client import base_policy as _base_policy
14
+ from . import msgpack_numpy
15
+
16
+ class WebsocketPolicyServer:
17
+ """Serves a policy using the websocket protocol. See websocket_client_policy.py for a client implementation.
18
+
19
+ Currently only implements the `load` and `infer` methods.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ policy,
25
+ host: str = "0.0.0.0",
26
+ port: int = 10093,
27
+ idle_timeout: int = -1, # 新增参数,单位秒,-1表示永不关闭
28
+ metadata: dict | None = None,
29
+
30
+ ) -> None:
31
+ self._policy = policy #
32
+ self._host = host
33
+ self._port = port
34
+ self._metadata = metadata or {}
35
+ self._idle_timeout = idle_timeout
36
+ self._last_active = time.time()
37
+ logging.getLogger("websockets.server").setLevel(logging.INFO)
38
+
39
+ def serve_forever(self) -> None:
40
+ asyncio.run(self.run())
41
+
42
+ async def run(self):
43
+ async with websockets.asyncio.server.serve(
44
+ self._handler,
45
+ self._host,
46
+ self._port,
47
+ compression=None,
48
+ max_size=None,
49
+ ) as server:
50
+ if self._idle_timeout > 0:
51
+ await self._idle_watchdog(server)
52
+ else:
53
+ await server.serve_forever()
54
+
55
+ async def _idle_watchdog(self, server):
56
+ """监控空闲时间,超时则关闭服务器"""
57
+ while True:
58
+ await asyncio.sleep(5)
59
+ if time.time() - self._last_active > self._idle_timeout:
60
+ logging.info(f"Idle timeout ({self._idle_timeout}s) reached, shutting down server.")
61
+ server.close()
62
+ await server.wait_closed()
63
+ break
64
+
65
+ async def _handler(self, websocket: websockets.asyncio.server.ServerConnection):
66
+ logging.info(f"Connection from {websocket.remote_address} opened")
67
+ packer = msgpack_numpy.Packer()
68
+
69
+ await websocket.send(packer.pack(self._metadata))
70
+
71
+ while True:
72
+ try:
73
+ msg = msgpack_numpy.unpackb(await websocket.recv())
74
+ self._last_active = time.time() # 每次收到消息刷新活跃时间
75
+ ret = self._route_message(msg) # route message
76
+ await websocket.send(packer.pack(ret))
77
+ except websockets.ConnectionClosed:
78
+ logging.info(f"Connection from {websocket.remote_address} closed")
79
+ break
80
+ except Exception:
81
+ await websocket.send(traceback.format_exc())
82
+ await websocket.close(
83
+ code=websockets.frames.CloseCode.INTERNAL_ERROR,
84
+ reason="Internal server error. Traceback included in previous frame.",
85
+ )
86
+ raise
87
+
88
+ # route logic: recognize request from client
89
+ def _route_message(self, msg: dict) -> dict:
90
+ """
91
+ Route rules (fault-tolerant):
92
+ - Supports messages of form:
93
+ {"type": "ping|init|infer|reset", "request_id": "...", "payload": {...}}
94
+ or a flat dict (will be treated as payload).
95
+ - Does NOT raise inside this function: all exceptions are caught and encoded in response.
96
+ """
97
+ req_id = msg.get("request_id", "default")
98
+ mtype = msg.get("type", "infer") # default = infer
99
+ msg # when no explicit payload, treat top-level as payload
100
+
101
+ # ping
102
+ if mtype == "ping":
103
+ return {"status": "ok", "ok": True, "type": "ping", "request_id": req_id}
104
+
105
+ # infer --> framework.predict_action
106
+ elif mtype == "infer" or mtype == "predict_action":
107
+ # Basic payload sanity
108
+ if not isinstance(msg, dict):
109
+ return {
110
+ "status": "error",
111
+ "ok": False,
112
+ "type": "inference_result",
113
+ "request_id": req_id,
114
+ "error": {"message": "Payload must be a dict", "payload_type": str(type(payload))}
115
+ }
116
+ try:
117
+
118
+ ouput_dict = self._policy.predict_action(**msg)
119
+ except Exception as e:
120
+ logging.exception("Policy inference error (request_id=%s)", req_id)
121
+ logging.exception(e)
122
+
123
+ return {
124
+ "status": "error",
125
+ "ok": False,
126
+ "type": "inference_result",
127
+ "request_id": req_id,
128
+ "error": {
129
+ "message": str(e),
130
+ # "traceback": traceback.format_exc(),
131
+ },
132
+ }
133
+ data = ouput_dict
134
+ return {
135
+ "status": "ok",
136
+ "ok": True,
137
+ "type": "inference_result",
138
+ "request_id": req_id,
139
+ "data": data,
140
+ }
141
+
142
+ # unknow request type
143
+ else:
144
+ return {
145
+ "status": "error",
146
+ "ok": False,
147
+ "type": "unknown",
148
+ "request_id": req_id,
149
+ "error": {"message": f"Unsupported message type '{mtype}'"},
150
+ }
151
+
152
+
153
+ if __name__ == "__main__":
154
+ logging.basicConfig(level=logging.INFO, force=True)
155
+ # Example usage:
156
+ # policy = YourPolicyClass() # Replace with your actual policy class
157
+ # server = WebsocketPolicyServer(policy, host="localhost", port=10091)
158
+ # server.serve_forever()
159
+ raise NotImplementedError("This module is not intended to be run directly.")
160
+ #
161
+ # Instead, it should be imported and used in a server context.
examples/SimplerEnv/README.md ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This document provides instructions for reproducing our **experimental results** with SimplerEnv.
2
+
3
+
4
+ The evaluation process consists of two main parts:
5
+
6
+ 1. Setting up the `simpler_env` environment and dependencies.
7
+ 2. Running the evaluation by launching services in both `starVLA` and `simpler_env` environments.
8
+
9
+ We have verified that this workflow runs successfully on both **NVIDIA A100** and **RTX 4090** GPUs.
10
+
11
+ ## 📦 1. Environment Setup
12
+
13
+ To set up the environment, please first follow the official [SimplerEnv repository](https://github.com/simpler-env/SimplerEnv) to install the base `simpler_env` environment.
14
+
15
+ Afterwards, inside the `simpler_env` environment, install the following dependencies:
16
+
17
+ ```bash
18
+ conda activate simpler_env
19
+ pip install tyro matplotlib mediapy websockets msgpack
20
+ pip install numpy==1.24.4
21
+ ```
22
+
23
+ ⚠️ **Common Issues**
24
+ When testing SimplerEnv on NVIDIA A100, you may encounter the following error:
25
+ `libvulkan.so.1: cannot open shared object file: No such file or directory`
26
+ You can refer to this link to fix: [Installation Guide – Vulkan Section](https://maniskill.readthedocs.io/en/latest/user_guide/getting_started/installation.html#vulkan)
27
+
28
+
29
+ ## 🔧 Verification Your SimplerEnv
30
+ We provide a minimal environment verification script:
31
+
32
+ ```bash
33
+ python examples/SimplerEnv/test_your_simplerEnv.py
34
+ ```
35
+
36
+ If you see the "✅ Env built successfully" message, it means SimplerEnv is installed correctly and ready to use.
37
+
38
+
39
+ ---
40
+
41
+
42
+ ## 🚀 2. Eval SimplerEnv
43
+
44
+ The evaluation should be run **from the repository root** using **two separate terminals/machines**, one for each environment:
45
+
46
+ - **starVLA environment**: runs the policy inference server.
47
+ - **simpler_env environment**: runs the simulation eval code.
48
+
49
+
50
+ ### Step 0. Download offical checkpoint
51
+
52
+ Available SimplerEnv WidowX checkpoints (see [docs/model_zoo.md](../../docs/model_zoo.md) for the full list):
53
+
54
+ - [Qwen3VL-GR00T-Bridge-RT-1](https://huggingface.co/StarVLA/Qwen3VL-GR00T-Bridge-RT-1) — 65.3 avg success
55
+ - [Qwen3VL-PI_v3-Bridge-RT-1](https://huggingface.co/StarVLA/Qwen3VL-PI_v3-Bridge-RT_1) — **69.8 avg success** (layer-wise cross-DiT flow-matching head, π₀.₅-style)
56
+
57
+
58
+
59
+ ### Step 1. Start the server (starVLA environment)
60
+
61
+ In the first terminal, activate the `starVLA` conda environment and run:
62
+
63
+ ```bash
64
+ bash examples/SimplerEnv/eval_files/run_policy_server.sh
65
+ ```
66
+
67
+ ⚠️ **Note:** Please ensure that you specify the correct checkpoint path in
68
+ `run_policy_server.sh`
69
+
70
+ ---
71
+
72
+
73
+ ### Step 2. Start the simulation (simpler_env environment)
74
+
75
+ In the second terminal, activate the `simpler_env` conda environment and run:
76
+
77
+ ```bash
78
+ export MODEL_PATH=.../checkpoints/steps_50000_pytorch_model.pt # for read normonization json and get name to save video under ckpt dir
79
+ bash examples/SimplerEnv/start_simpler_env.sh ${MODEL_PATH}
80
+ ```
81
+ This script will automatically launch the WidowX Robot evaluation tasks, reproducing the benchmark results reported above.
82
+
83
+ ⚠️ **Note:** Please ensure that you specify the correct `SimplerEnv_PATH`in
84
+ `start_simpler_env.sh`
85
+
86
+
87
+ ⚠️ **Common Issues**
88
+
89
+ When run policy server but `NotImplementedError:Framework QwenGR00T is not implemented`, you may need to `python QwenGR00T.py` to check your env.
90
+
91
+
92
+
93
+ # 🚀 Training on OXE
94
+
95
+ ## Data Preparation
96
+
97
+
98
+ Steps:
99
+ 1) Download a LeRobot-format OXE dataset
100
+ - [bridge_orig_lerobot](https://huggingface.co/datasets/IPEC-COMMUNITY/bridge_orig_lerobot)
101
+ - [fractal20220817_data_lerobot](https://huggingface.co/datasets/IPEC-COMMUNITY/fractal20220817_data_lerobot)
102
+
103
+ 2) Including `modality.json` in each `*lerobot/meta/modality.json`
104
+ - [bridge modality](./train_files/modality.json). Rename as modality.json and put it as `bridge_orig_lerobot/meta/modality.json`
105
+ - [fractal modality](./train_files/fractal_modality.json). Rename as `modality.json` and put it as `fractal20220817_data_lerobot/meta/modality.json`
106
+
107
+ 3) Add your dataset path to `config.yaml`:
108
+ ```yaml
109
+ datasets:
110
+ vla_data:
111
+ dataset_py: lerobot_datasets
112
+ data_root_dir: playground/Datasets/OXE_LEROBOT_DATASET # path to your dataset
113
+ data_mix: bridge_rt_1
114
+ ```
115
+
116
+
117
+ ### Check Your Dataoader
118
+ We provide a simple way to check your dataloader. Make sure you can load batched data:
119
+
120
+ ```bash
121
+ python starVLA/dataloader/lerobot_datasets.py --config_yaml examples/SimplerEnv/train_files/starvla_cotrain_oxe.yaml
122
+ ```
123
+
124
+ ## Framework Preparation
125
+
126
+ Before running, you need to ensure that your framework can `forward` and `predict_action` using a fake data example.
127
+
128
+ Try the following command:
129
+
130
+ ```bash
131
+ python starVLA/model/framework/VLM4A/QwenGR00T.py --config_yaml examples/SimplerEnv/train_files/starvla_cotrain_oxe.yaml
132
+ ```
133
+
134
+ Note: You can modify the following code snippet to align with your dataset:
135
+
136
+ ```python
137
+ # Generate a fake sample
138
+ image = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))
139
+ # Create a sample
140
+ sample = {
141
+ "action": np.random.uniform(-1, 1, size=(16, 7)).astype(np.float16), # action_chunk, action_dim
142
+ "image": [image, image], # two views
143
+ "lang": "This is a fake for testing.",
144
+ "state": np.random.uniform(-1, 1, size=(1, 7)).astype(np.float16), # chunk, state_dim
145
+ }
146
+ ```
147
+
148
+ ## Training
149
+
150
+ Once everything is ready, use our provided script to start training:
151
+
152
+ ```bash
153
+ bash ./examples/SimplerEnv/train_files/run_oxe_train.sh
154
+ ```
155
+
156
+ ⚠️ **Note:** Ensure that the script explicitly uses the validated config path in `run_lerobot_datasets.sh`. If not already passed, add the `--config_yaml` argument.
157
+
158
+
examples/SimplerEnv/eval_files/adaptive_ensemble.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ adaptive_ensemble.py
3
+
4
+ """
5
+
6
+ from collections import deque
7
+
8
+ import numpy as np
9
+
10
+
11
+ class AdaptiveEnsembler:
12
+ def __init__(self, pred_action_horizon, adaptive_ensemble_alpha=0.0):
13
+ self.pred_action_horizon = pred_action_horizon
14
+ self.action_history = deque(maxlen=self.pred_action_horizon)
15
+ self.adaptive_ensemble_alpha = adaptive_ensemble_alpha
16
+
17
+ def reset(self):
18
+ self.action_history.clear()
19
+
20
+ def ensemble_action(self, cur_action):
21
+ self.action_history.append(cur_action)
22
+ num_actions = len(self.action_history)
23
+ if cur_action.ndim == 1:
24
+ curr_act_preds = np.stack(self.action_history)
25
+ else:
26
+ curr_act_preds = np.stack(
27
+ [pred_actions[i] for (i, pred_actions) in zip(range(num_actions - 1, -1, -1), self.action_history)]
28
+ )
29
+
30
+ # calculate cosine similarity between the current prediction and all previous predictions
31
+ ref = curr_act_preds[num_actions - 1, :]
32
+ previous_pred = curr_act_preds
33
+ dot_product = np.sum(previous_pred * ref, axis=1)
34
+ norm_previous_pred = np.linalg.norm(previous_pred, axis=1)
35
+ norm_ref = np.linalg.norm(ref)
36
+ cos_similarity = dot_product / (norm_previous_pred * norm_ref + 1e-7)
37
+
38
+ # compute the weights for each prediction
39
+ weights = np.exp(self.adaptive_ensemble_alpha * cos_similarity)
40
+ weights = weights / weights.sum()
41
+
42
+ # compute the weighted average across all predictions for this timestep
43
+ cur_action = np.sum(weights[:, None] * curr_act_preds, axis=0)
44
+
45
+ return cur_action
examples/SimplerEnv/eval_files/custom_argparse.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # copy from SimplerEnv/simpler_env/evaluation/argparse.py
2
+
3
+ import argparse
4
+
5
+ import numpy as np
6
+ from sapien.core import Pose
7
+ from simpler_env.utils.io import DictAction
8
+ from transforms3d.euler import euler2quat
9
+
10
+
11
+ def parse_range_tuple(t):
12
+ return np.linspace(t[0], t[1], int(t[2]))
13
+
14
+
15
+ def get_args():
16
+ # parse command-line arguments
17
+ parser = argparse.ArgumentParser()
18
+ parser.add_argument(
19
+ "--policy-model",
20
+ type=str,
21
+ default="rt1",
22
+ help="Policy model type; e.g., 'rt1', 'octo-base', 'octo-small'",
23
+ )
24
+ parser.add_argument(
25
+ "--policy-setup",
26
+ type=str,
27
+ default="google_robot",
28
+ help="Policy model setup; e.g., 'google_robot', 'widowx_bridge'",
29
+ )
30
+ parser.add_argument("--ckpt-path", type=str, default=None)
31
+ parser.add_argument("--env-name", type=str, required=True)
32
+ parser.add_argument(
33
+ "--additional-env-save-tags",
34
+ type=str,
35
+ default=None,
36
+ help="Additional tags to save the environment eval results",
37
+ )
38
+ parser.add_argument("--scene-name", type=str, default="google_pick_coke_can_1_v4")
39
+ parser.add_argument("--enable-raytracing", action="store_true")
40
+ parser.add_argument("--robot", type=str, default="google_robot_static")
41
+ parser.add_argument(
42
+ "--obs-camera-name",
43
+ type=str,
44
+ default=None,
45
+ help="Obtain image observation from this camera for policy input. None = default",
46
+ )
47
+ parser.add_argument("--action-scale", type=float, default=1.0)
48
+
49
+ parser.add_argument("--control-freq", type=int, default=3)
50
+ parser.add_argument("--sim-freq", type=int, default=513)
51
+ parser.add_argument("--max-episode-steps", type=int, default=80)
52
+ parser.add_argument("--rgb-overlay-path", type=str, default=None)
53
+ parser.add_argument(
54
+ "--robot-init-x-range",
55
+ type=float,
56
+ nargs=3,
57
+ default=[0.35, 0.35, 1],
58
+ help="[xmin, xmax, num]",
59
+ )
60
+ parser.add_argument(
61
+ "--robot-init-y-range",
62
+ type=float,
63
+ nargs=3,
64
+ default=[0.20, 0.20, 1],
65
+ help="[ymin, ymax, num]",
66
+ )
67
+ parser.add_argument(
68
+ "--robot-init-rot-quat-center",
69
+ type=float,
70
+ nargs=4,
71
+ default=[1, 0, 0, 0],
72
+ help="[x, y, z, w]",
73
+ )
74
+ parser.add_argument(
75
+ "--robot-init-rot-rpy-range",
76
+ type=float,
77
+ nargs=9,
78
+ default=[0, 0, 1, 0, 0, 1, 0, 0, 1],
79
+ help="[rmin, rmax, rnum, pmin, pmax, pnum, ymin, ymax, ynum]",
80
+ )
81
+ parser.add_argument(
82
+ "--obj-variation-mode",
83
+ type=str,
84
+ default="xy",
85
+ choices=["xy", "episode"],
86
+ help="Whether to vary the xy position of a single object, or to vary predetermined episodes",
87
+ )
88
+ parser.add_argument("--obj-episode-range", type=int, nargs=2, default=[0, 60], help="[start, end]")
89
+ parser.add_argument(
90
+ "--obj-init-x-range",
91
+ type=float,
92
+ nargs=3,
93
+ default=[-0.35, -0.12, 5],
94
+ help="[xmin, xmax, num]",
95
+ )
96
+ parser.add_argument(
97
+ "--obj-init-y-range",
98
+ type=float,
99
+ nargs=3,
100
+ default=[-0.02, 0.42, 5],
101
+ help="[ymin, ymax, num]",
102
+ )
103
+
104
+ parser.add_argument(
105
+ "--additional-env-build-kwargs",
106
+ nargs="+",
107
+ action=DictAction,
108
+ help="Additional env build kwargs in xxx=yyy format. If the value "
109
+ 'is a list, it should be like key="[a,b]" or key=a,b '
110
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
111
+ "Note that the quotation marks are necessary and that no white space "
112
+ "is allowed.",
113
+ )
114
+ parser.add_argument("--logging-dir", type=str, default="./results")
115
+ parser.add_argument("--tf-memory-limit", type=int, default=3072, help="Tensorflow memory limit")
116
+ parser.add_argument("--octo-init-rng", type=int, default=0, help="Octo init rng seed")
117
+ parser.add_argument("--async-freq", type=int, default=1)
118
+ parser.add_argument("--host", type=str, default="127.0.0.1", help="Octo init rng seed")
119
+ parser.add_argument("--port", type=int, default=10093)
120
+ parser.add_argument("--result-json", type=str, default=None)
121
+
122
+ args = parser.parse_args()
123
+
124
+ # env args: robot pose
125
+ args.robot_init_xs = parse_range_tuple(args.robot_init_x_range)
126
+ args.robot_init_ys = parse_range_tuple(args.robot_init_y_range)
127
+ args.robot_init_quats = []
128
+ for r in parse_range_tuple(args.robot_init_rot_rpy_range[:3]):
129
+ for p in parse_range_tuple(args.robot_init_rot_rpy_range[3:6]):
130
+ for y in parse_range_tuple(args.robot_init_rot_rpy_range[6:]):
131
+ args.robot_init_quats.append((Pose(q=euler2quat(r, p, y)) * Pose(q=args.robot_init_rot_quat_center)).q)
132
+ # env args: object position
133
+ if args.obj_variation_mode == "xy":
134
+ args.obj_init_xs = parse_range_tuple(args.obj_init_x_range)
135
+ args.obj_init_ys = parse_range_tuple(args.obj_init_y_range)
136
+ # update logging info (args.additional_env_save_tags) if using a different camera from default
137
+ if args.obs_camera_name is not None:
138
+ if args.additional_env_save_tags is None:
139
+ args.additional_env_save_tags = f"obs_camera_{args.obs_camera_name}"
140
+ else:
141
+ args.additional_env_save_tags = args.additional_env_save_tags + f"_obs_camera_{args.obs_camera_name}"
142
+
143
+ return args
examples/SimplerEnv/eval_files/model2simpler_interface.py ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import deque
2
+ from typing import Optional, Sequence
3
+ import os
4
+ import json
5
+ import cv2 as cv
6
+ import matplotlib.pyplot as plt
7
+ import numpy as np
8
+ from transforms3d.euler import euler2axangle
9
+ from typing import Dict
10
+ import numpy as np
11
+ from pathlib import Path
12
+
13
+
14
+ from deployment.model_server.tools.websocket_policy_client import WebsocketClientPolicy
15
+ from examples.SimplerEnv.eval_files.adaptive_ensemble import AdaptiveEnsembler
16
+
17
+
18
+
19
+ class ModelClient:
20
+ def __init__(
21
+ self,
22
+ policy_ckpt_path,
23
+ unnorm_key: Optional[str] = None,
24
+ policy_setup: str = "widowx_bridge",
25
+ horizon: int = 0,
26
+ action_ensemble_horizon: Optional[int] = None,
27
+ image_size: list[int] = [224, 224],
28
+ action_scale: float = 1.0,
29
+ cfg_scale: float = 1.5,
30
+ use_ddim: bool = True,
31
+ num_ddim_steps: int = 10,
32
+ action_ensemble = True,
33
+ adaptive_ensemble_alpha = 0.1,
34
+ host="0.0.0.0",
35
+ port=10093,
36
+ ) -> None:
37
+
38
+ # build client to connect server policy
39
+ self.client = WebsocketClientPolicy(host, port)
40
+ self.server_metadata = self.client.get_server_metadata()
41
+
42
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
43
+ if policy_setup == "widowx_bridge":
44
+ unnorm_key = "oxe_bridge" if unnorm_key is None else unnorm_key
45
+ action_ensemble = action_ensemble
46
+ adaptive_ensemble_alpha = adaptive_ensemble_alpha
47
+ if action_ensemble_horizon is None:
48
+ # Set 7 for widowx_bridge to fix the window size of motion scale between each frame. see appendix in our paper for details
49
+ action_ensemble_horizon = 7
50
+ self.sticky_gripper_num_repeat = 1
51
+ elif policy_setup == "google_robot":
52
+ unnorm_key = "oxe_rt1" if unnorm_key is None else unnorm_key
53
+ action_ensemble = action_ensemble
54
+ adaptive_ensemble_alpha = adaptive_ensemble_alpha
55
+ if action_ensemble_horizon is None:
56
+ # Set 2 for google_robot to fix the window size of motion scale between each frame. see appendix in our paper for details
57
+ action_ensemble_horizon = 2
58
+ self.sticky_gripper_num_repeat = 10
59
+ else:
60
+ raise NotImplementedError(
61
+ f"Policy setup {policy_setup} not supported for octo models. The other datasets can be found in the huggingface config.json file."
62
+ )
63
+ self.policy_setup = policy_setup
64
+ self.unnorm_key = unnorm_key
65
+
66
+ print(f"*** policy_setup: {policy_setup}, unnorm_key: {unnorm_key} ***")
67
+ self.use_ddim = use_ddim
68
+ self.num_ddim_steps = num_ddim_steps
69
+
70
+
71
+ self.cfg_scale = cfg_scale # 1.5
72
+
73
+ self.image_size = image_size
74
+ self.action_scale = action_scale # 1.0
75
+ self.horizon = horizon #0
76
+ self.action_ensemble = action_ensemble
77
+ self.adaptive_ensemble_alpha = adaptive_ensemble_alpha
78
+ self.action_ensemble_horizon = action_ensemble_horizon
79
+ self.sticky_action_is_on = False
80
+ self.gripper_action_repeat = 0
81
+ self.sticky_gripper_action = 0.0
82
+ self.previous_gripper_action = None
83
+
84
+ self.task_description = None
85
+ self.image_history = deque(maxlen=self.horizon)
86
+ if self.action_ensemble:
87
+ self.action_ensembler = AdaptiveEnsembler(self.action_ensemble_horizon, self.adaptive_ensemble_alpha)
88
+ else:
89
+ self.action_ensembler = None
90
+ self.num_image_history = 0
91
+
92
+ self.action_norm_stats = self.get_action_stats(self.unnorm_key, policy_ckpt_path=policy_ckpt_path)
93
+
94
+
95
+ def _add_image_to_history(self, image: np.ndarray) -> None:
96
+ self.image_history.append(image)
97
+ self.num_image_history = min(self.num_image_history + 1, self.horizon)
98
+
99
+ def reset(self, task_description: str) -> None:
100
+ self.task_description = task_description
101
+ self.image_history.clear()
102
+ if self.action_ensemble:
103
+ self.action_ensembler.reset()
104
+ self.num_image_history = 0
105
+
106
+ self.sticky_action_is_on = False
107
+ self.gripper_action_repeat = 0
108
+ self.sticky_gripper_action = 0.0
109
+ self.previous_gripper_action = None
110
+
111
+ def step(
112
+ self, image: np.ndarray, task_description: Optional[str] = None, *args, **kwargs
113
+ ) -> tuple[dict[str, np.ndarray], dict[str, np.ndarray]]:
114
+ """
115
+ Input:
116
+ image: np.ndarray of shape (H, W, 3), uint8
117
+ task_description: Optional[str], task description; if different from previous task description, policy state is reset
118
+ Output:
119
+ raw_action: dict; raw policy action output
120
+ action: dict; processed action to be sent to the maniskill2 environment, with the following keys:
121
+ - 'world_vector': np.ndarray of shape (3,), xyz translation of robot end-effector
122
+ - 'rot_axangle': np.ndarray of shape (3,), axis-angle representation of end-effector rotation
123
+ - 'gripper': np.ndarray of shape (1,), gripper action
124
+ - 'terminate_episode': np.ndarray of shape (1,), 1 if episode should be terminated, 0 otherwise
125
+ """
126
+ if task_description is not None:
127
+ if task_description != self.task_description:
128
+ self.reset(task_description)
129
+
130
+ assert image.dtype == np.uint8
131
+ self._add_image_to_history(self._resize_image(image))
132
+ # image: Image.Image = Image.fromarray(image)
133
+
134
+ image = self._resize_image(image)
135
+ example = {
136
+ "image": [image],
137
+ "lang": self.task_description,
138
+ }
139
+
140
+ vla_input = {
141
+ "examples": [example],
142
+ "do_sample": False,
143
+ "cfg_scale": self.cfg_scale,
144
+ "use_ddim": self.use_ddim,
145
+ "num_ddim_steps": self.num_ddim_steps,
146
+ }
147
+
148
+ vla_input = {
149
+ "examples": [example],
150
+ "do_sample": False,
151
+ "use_ddim": self.use_ddim,
152
+ "num_ddim_steps": self.num_ddim_steps,
153
+ # Multi-dataset checkpoints require an explicit statistics selector.
154
+ "unnorm_key": self.unnorm_key,
155
+ }
156
+
157
+
158
+ response = self.client.predict_action(vla_input)
159
+
160
+
161
+ # The policy server returns actions already unnormalized with the selected dataset statistics.
162
+ raw_actions = np.asarray(response["data"]["actions"][0])
163
+
164
+ if self.action_ensemble:
165
+ raw_actions = self.action_ensembler.ensemble_action(raw_actions)[None]
166
+
167
+ raw_action = {
168
+ "world_vector": np.array(raw_actions[0, :3]),
169
+ "rotation_delta": np.array(raw_actions[0, 3:6]),
170
+ "open_gripper": np.array(raw_actions[0, 6:7]), # range [0, 1]; 1 = open; 0 = close
171
+ }
172
+
173
+ # process raw_action to obtain the action to be sent to the maniskill2 environment
174
+ action = {}
175
+ action["world_vector"] = raw_action["world_vector"] * self.action_scale
176
+ action_rotation_delta = np.asarray(raw_action["rotation_delta"], dtype=np.float64)
177
+
178
+ roll, pitch, yaw = action_rotation_delta
179
+ axes, angles = euler2axangle(roll, pitch, yaw)
180
+ action_rotation_axangle = axes * angles
181
+ action["rot_axangle"] = action_rotation_axangle * self.action_scale
182
+
183
+ if self.policy_setup == "google_robot":
184
+ action["gripper"] = 0
185
+ current_gripper_action = raw_action["open_gripper"]
186
+ if self.previous_gripper_action is None:
187
+ relative_gripper_action = np.array([0])
188
+ self.previous_gripper_action = current_gripper_action
189
+ else:
190
+ relative_gripper_action = self.previous_gripper_action - current_gripper_action
191
+ # fix a bug in the SIMPLER code here
192
+ # self.previous_gripper_action = current_gripper_action
193
+
194
+ if np.abs(relative_gripper_action) > 0.5 and (not self.sticky_action_is_on):
195
+ self.sticky_action_is_on = True
196
+ self.sticky_gripper_action = relative_gripper_action
197
+ self.previous_gripper_action = current_gripper_action
198
+
199
+ if self.sticky_action_is_on:
200
+ self.gripper_action_repeat += 1
201
+ relative_gripper_action = self.sticky_gripper_action
202
+
203
+ if self.gripper_action_repeat == self.sticky_gripper_num_repeat:
204
+ self.sticky_action_is_on = False
205
+ self.gripper_action_repeat = 0
206
+ self.sticky_gripper_action = 0.0
207
+
208
+ action["gripper"] = relative_gripper_action
209
+
210
+ elif self.policy_setup == "widowx_bridge":
211
+ action["gripper"] = 2.0 * (raw_action["open_gripper"] > 0.5) - 1.0
212
+
213
+ action["terminate_episode"] = np.array([0.0])
214
+ return raw_action, action
215
+
216
+ @staticmethod
217
+ def unnormalize_actions(normalized_actions: np.ndarray, action_norm_stats: Dict[str, np.ndarray]) -> np.ndarray:
218
+ mask = action_norm_stats.get("mask", np.ones_like(action_norm_stats["q01"], dtype=bool))
219
+ action_high, action_low = np.array(action_norm_stats["q99"]), np.array(action_norm_stats["q01"])
220
+ normalized_actions = np.clip(normalized_actions, -1, 1)
221
+ normalized_actions[:, 6] = np.where(normalized_actions[:, 6] < 0.5, 0, 1)
222
+ actions = np.where(
223
+ mask,
224
+ 0.5 * (normalized_actions + 1) * (action_high - action_low) + action_low,
225
+ normalized_actions,
226
+ )
227
+
228
+ return actions
229
+
230
+ @staticmethod
231
+ def get_action_stats(unnorm_key: str, policy_ckpt_path) -> dict:
232
+ """
233
+ Duplicate stats accessor (retained for backward compatibility).
234
+ """
235
+ policy_ckpt_path = Path(policy_ckpt_path)
236
+ norm_stats = json.loads((policy_ckpt_path.parents[1] / "dataset_statistics.json").read_text())
237
+ # unnorm_key = baseframework._check_unnorm_key(norm_stats, unnorm_key) # 其实也是很环境 specific 的
238
+ return norm_stats[unnorm_key]["action"]
239
+
240
+
241
+
242
+ def _resize_image(self, image: np.ndarray) -> np.ndarray:
243
+ image = cv.resize(image, tuple(self.image_size), interpolation=cv.INTER_AREA)
244
+ return image
245
+
246
+ def visualize_epoch(
247
+ self, predicted_raw_actions: Sequence[np.ndarray], images: Sequence[np.ndarray], save_path: str
248
+ ) -> None:
249
+ images = [self._resize_image(image) for image in images]
250
+ ACTION_DIM_LABELS = ["x", "y", "z", "roll", "pitch", "yaw", "grasp"]
251
+
252
+ img_strip = np.concatenate(np.array(images[::3]), axis=1)
253
+
254
+ # set up plt figure
255
+ figure_layout = [["image"] * len(ACTION_DIM_LABELS), ACTION_DIM_LABELS]
256
+ plt.rcParams.update({"font.size": 12})
257
+ fig, axs = plt.subplot_mosaic(figure_layout)
258
+ fig.set_size_inches([45, 10])
259
+
260
+ # plot actions
261
+ pred_actions = np.array(
262
+ [
263
+ np.concatenate([a["world_vector"], a["rotation_delta"], a["open_gripper"]], axis=-1)
264
+ for a in predicted_raw_actions
265
+ ]
266
+ )
267
+ for action_dim, action_label in enumerate(ACTION_DIM_LABELS):
268
+ # actions have batch, horizon, dim, in this example we just take the first action for simplicity
269
+ axs[action_label].plot(pred_actions[:, action_dim], label="predicted action")
270
+ axs[action_label].set_title(action_label)
271
+ axs[action_label].set_xlabel("Time in one episode")
272
+
273
+ axs["image"].imshow(img_strip)
274
+ axs["image"].set_xlabel("Time in one episode (subsampled)")
275
+ plt.legend()
276
+ plt.savefig(save_path)
examples/SimplerEnv/eval_files/start_simpler_env.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+
5
+ import numpy as np
6
+ from simpler_env.evaluation.maniskill2_evaluator import maniskill2_evaluator
7
+
8
+ # from IPython import embed; embed()
9
+ from examples.SimplerEnv.eval_files.bridgert_ik_trace import install_ik_trace
10
+ from examples.SimplerEnv.eval_files.custom_argparse import get_args
11
+ from examples.SimplerEnv.eval_files.model2simpler_interface import ModelClient
12
+
13
+
14
+ def start_debugpy_once():
15
+ import debugpy
16
+
17
+ if getattr(start_debugpy_once, "_started", False):
18
+ return
19
+ debugpy.listen(("0.0.0.0", 10092))
20
+ print("🔍 Waiting for VSCode attach on 0.0.0.0:10092 ...")
21
+ debugpy.wait_for_client()
22
+ start_debugpy_once._started = True
23
+
24
+
25
+ if __name__ == "__main__":
26
+ args = get_args()
27
+
28
+ os.environ["DISPLAY"] = ""
29
+ # prevent a single jax process from taking up all the GPU memory
30
+ os.environ["XLA_PYTHON_CLIENT_PREALLOCATE"] = "false"
31
+
32
+ install_ik_trace()
33
+ if os.getenv("DEBUG", False):
34
+ start_debugpy_once()
35
+ model = ModelClient(
36
+ policy_ckpt_path=args.ckpt_path, # to get unnormalization stats
37
+ policy_setup=args.policy_setup,
38
+ port=args.port,
39
+ action_scale=args.action_scale,
40
+ cfg_scale=1.5, # cfg from 1.5 to 7 also performs well
41
+ )
42
+
43
+ # policy model creation; update this if you are using a new policy model
44
+ # run real-to-sim evaluation
45
+ success_arr = maniskill2_evaluator(model, args)
46
+ print(args)
47
+ print(" " * 10, "Average success", np.mean(success_arr))
48
+ if args.result_json:
49
+ result = {
50
+ "schema_version": 2,
51
+ "env_name": args.env_name,
52
+ "episode_start": args.obj_episode_range[0],
53
+ "episode_end": args.obj_episode_range[1],
54
+ "episode_indices": list(range(*args.obj_episode_range)),
55
+ "checkpoint": str(Path(args.ckpt_path).resolve()),
56
+ "server_metadata": getattr(model, "server_metadata", {}),
57
+ "successes": np.asarray(success_arr, dtype=bool).tolist(),
58
+ }
59
+ destination = Path(args.result_json)
60
+ destination.parent.mkdir(parents=True, exist_ok=True)
61
+ temporary = destination.with_suffix(destination.suffix + ".tmp")
62
+ temporary.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8")
63
+ os.replace(temporary, destination)
examples/SimplerEnv/eval_files/test_your_simplerEnv.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Optional: print more diagnostics for debugging
2
+ # from sapien import disable_renderer
3
+ # disable_renderer() # <-- Uncomment to skip the renderer
4
+
5
+
6
+ import logging
7
+ import os
8
+
9
+ import simpler_env
10
+ from simpler_env.utils.env.env_builder import build_maniskill2_env
11
+
12
+ logging.basicConfig(level=logging.DEBUG)
13
+
14
+ # Resolve rgb_overlay_path relative to the SimplerEnv package location
15
+ _simpler_env_root = os.path.dirname(os.path.dirname(simpler_env.__file__))
16
+ _overlay_path = os.path.join(_simpler_env_root, "ManiSkill2_real2sim/data/real_inpainting/bridge_sink.png")
17
+
18
+ env_name = "PutEggplantInBasketScene-v0"
19
+
20
+ kwargs = {
21
+ "obs_mode": "rgbd",
22
+ "robot": "widowx_sink_camera_setup",
23
+ "sim_freq": 500,
24
+ "control_mode": "arm_pd_ee_target_delta_pose_align2_gripper_pd_joint_pos",
25
+ "control_freq": 5,
26
+ "max_episode_steps": 120,
27
+ "scene_name": "bridge_table_1_v2",
28
+ "camera_cfgs": {"add_segmentation": True},
29
+ "rgb_overlay_path": _overlay_path,
30
+ }
31
+
32
+ additional_env_build_kwargs = {}
33
+
34
+ print("🔧 Start building ManiSkill2 env...")
35
+ env = build_maniskill2_env(
36
+ env_name,
37
+ **additional_env_build_kwargs,
38
+ **kwargs,
39
+ )
40
+ print("✅ Env built successfully:", env)
41
+
42
+ obs = env.reset()
43
+ print("📷 First observation keys:", obs.keys() if isinstance(obs, dict) else type(obs))
examples/SimplerEnv/train_files/data_registry/data_config.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """OXE (Open-X Embodiment) benchmark — data config, embodiment tags, and mixtures."""
2
+
3
+ from starVLA.dataloader.gr00t_lerobot.datasets import ModalityConfig
4
+ from starVLA.dataloader.gr00t_lerobot.transform.base import ComposedModalityTransform
5
+ from starVLA.dataloader.gr00t_lerobot.transform.concat import ConcatTransform
6
+ from starVLA.dataloader.gr00t_lerobot.transform.state_action import (
7
+ StateActionToTensor,
8
+ StateActionTransform,
9
+ )
10
+ from starVLA.dataloader.gr00t_lerobot.transform.video import (
11
+ VideoColorJitter,
12
+ VideoCrop,
13
+ VideoResize,
14
+ VideoToNumpy,
15
+ VideoToTensor,
16
+ )
17
+ from starVLA.dataloader.gr00t_lerobot.embodiment_tags import EmbodimentTag
18
+
19
+
20
+ # ---------------------------------------------------------------------------
21
+ # DataConfig — OXE Droid
22
+ # ---------------------------------------------------------------------------
23
+ class OxeDroidDataConfig:
24
+ embodiment_tag = EmbodimentTag.OXE_DROID
25
+ video_keys = [
26
+ "video.exterior_image_1",
27
+ "video.exterior_image_2",
28
+ "video.wrist_image",
29
+ ]
30
+ state_keys = [
31
+ "state.eef_position",
32
+ "state.eef_rotation",
33
+ "state.gripper_position",
34
+ ]
35
+ action_keys = [
36
+ "action.eef_position_delta",
37
+ "action.eef_rotation_delta",
38
+ "action.gripper_position",
39
+ ]
40
+ language_keys = ["annotation.language.language_instruction"]
41
+ observation_indices = [0]
42
+ action_indices = list(range(16))
43
+
44
+ def modality_config(self):
45
+ return {
46
+ "video": ModalityConfig(delta_indices=self.observation_indices, modality_keys=self.video_keys),
47
+ "state": ModalityConfig(delta_indices=self.observation_indices, modality_keys=self.state_keys),
48
+ "action": ModalityConfig(delta_indices=self.action_indices, modality_keys=self.action_keys),
49
+ "language": ModalityConfig(delta_indices=self.observation_indices, modality_keys=self.language_keys),
50
+ }
51
+
52
+ def transform(self):
53
+ return ComposedModalityTransform(transforms=[
54
+ VideoToTensor(apply_to=self.video_keys),
55
+ VideoCrop(apply_to=self.video_keys, scale=0.95),
56
+ VideoResize(apply_to=self.video_keys, height=224, width=224, interpolation="linear"),
57
+ VideoColorJitter(apply_to=self.video_keys, brightness=0.3, contrast=0.4, saturation=0.5, hue=0.08),
58
+ VideoToNumpy(apply_to=self.video_keys),
59
+ StateActionToTensor(apply_to=self.state_keys),
60
+ StateActionTransform(
61
+ apply_to=self.state_keys,
62
+ normalization_modes={"state.eef_position": "min_max", "state.gripper_position": "min_max"},
63
+ target_rotations={"state.eef_rotation": "rotation_6d"},
64
+ ),
65
+ StateActionToTensor(apply_to=self.action_keys),
66
+ StateActionTransform(
67
+ apply_to=self.action_keys,
68
+ normalization_modes={"action.gripper_position": "binary"},
69
+ target_rotations={"action.eef_rotation_delta": "axis_angle"},
70
+ ),
71
+ ConcatTransform(
72
+ video_concat_order=self.video_keys,
73
+ state_concat_order=self.state_keys,
74
+ action_concat_order=self.action_keys,
75
+ ),
76
+ ])
77
+
78
+
79
+ # ---------------------------------------------------------------------------
80
+ # DataConfig — OXE Bridge
81
+ # ---------------------------------------------------------------------------
82
+ class OxeBridgeDataConfig:
83
+ embodiment_tag = EmbodimentTag.OXE_BRIDGE
84
+ video_keys = ["video.image_0"]
85
+ state_keys = [
86
+ "state.x", "state.y", "state.z",
87
+ "state.roll", "state.pitch", "state.yaw",
88
+ "state.pad", "state.gripper",
89
+ ]
90
+ action_keys = [
91
+ "action.x", "action.y", "action.z",
92
+ "action.roll", "action.pitch", "action.yaw",
93
+ "action.gripper",
94
+ ]
95
+ language_keys = ["annotation.human.action.task_description"]
96
+ observation_indices = [0]
97
+ action_indices = list(range(16))
98
+
99
+ def modality_config(self):
100
+ return {
101
+ "video": ModalityConfig(delta_indices=self.observation_indices, modality_keys=self.video_keys),
102
+ "state": ModalityConfig(delta_indices=self.observation_indices, modality_keys=self.state_keys),
103
+ "action": ModalityConfig(delta_indices=self.action_indices, modality_keys=self.action_keys),
104
+ "language": ModalityConfig(delta_indices=self.observation_indices, modality_keys=self.language_keys),
105
+ }
106
+
107
+ def transform(self):
108
+ return ComposedModalityTransform(transforms=[
109
+ StateActionToTensor(apply_to=self.state_keys),
110
+ StateActionTransform(
111
+ apply_to=self.state_keys,
112
+ normalization_modes={k: ("binary" if k == "state.gripper" else "q99") for k in self.state_keys},
113
+ ),
114
+ StateActionToTensor(apply_to=self.action_keys),
115
+ StateActionTransform(
116
+ apply_to=self.action_keys,
117
+ normalization_modes={k: ("binary" if k == "action.gripper" else "q99") for k in self.action_keys},
118
+ ),
119
+ ])
120
+
121
+
122
+ # ---------------------------------------------------------------------------
123
+ # DataConfig — OXE RT-1
124
+ # ---------------------------------------------------------------------------
125
+ class OxeRT1DataConfig:
126
+ embodiment_tag = EmbodimentTag.OXE_RT1
127
+ video_keys = ["video.image"]
128
+ state_keys = [
129
+ "state.x", "state.y", "state.z",
130
+ "state.rx", "state.ry", "state.rz", "state.rw",
131
+ "state.gripper",
132
+ ]
133
+ action_keys = [
134
+ "action.x", "action.y", "action.z",
135
+ "action.roll", "action.pitch", "action.yaw",
136
+ "action.gripper",
137
+ ]
138
+ language_keys = ["annotation.human.action.task_description"]
139
+ observation_indices = [0]
140
+ action_indices = list(range(16))
141
+
142
+ def modality_config(self):
143
+ return {
144
+ "video": ModalityConfig(delta_indices=self.observation_indices, modality_keys=self.video_keys),
145
+ "state": ModalityConfig(delta_indices=self.observation_indices, modality_keys=self.state_keys),
146
+ "action": ModalityConfig(delta_indices=self.action_indices, modality_keys=self.action_keys),
147
+ "language": ModalityConfig(delta_indices=self.observation_indices, modality_keys=self.language_keys),
148
+ }
149
+
150
+ def transform(self):
151
+ return ComposedModalityTransform(transforms=[
152
+ StateActionToTensor(apply_to=self.state_keys),
153
+ StateActionTransform(
154
+ apply_to=self.state_keys,
155
+ normalization_modes={k: ("binary" if k == "state.gripper" else "q99") for k in self.state_keys},
156
+ ),
157
+ StateActionToTensor(apply_to=self.action_keys),
158
+ StateActionTransform(
159
+ apply_to=self.action_keys,
160
+ normalization_modes={k: ("binary" if k == "action.gripper" else "q99") for k in self.action_keys},
161
+ ),
162
+ ])
163
+
164
+
165
+ ROBOT_TYPE_CONFIG_MAP = {
166
+ "oxe_droid": OxeDroidDataConfig(),
167
+ "oxe_bridge": OxeBridgeDataConfig(),
168
+ "oxe_rt1": OxeRT1DataConfig(),
169
+ }
170
+
171
+
172
+ # ---------------------------------------------------------------------------
173
+ # Embodiment Tags
174
+ # ---------------------------------------------------------------------------
175
+ ROBOT_TYPE_TO_EMBODIMENT_TAG = {
176
+ # Per Proposal A, embodiment_tag now lives as a classvar on each DataConfig.
177
+ # The registry derives ROBOT_TYPE_TO_EMBODIMENT_TAG automatically. Kept as
178
+ # an empty dict for backward compat (it is honored as legacy override).
179
+ }
180
+
181
+
182
+ # ---------------------------------------------------------------------------
183
+ # Mixtures
184
+ # ---------------------------------------------------------------------------
185
+ DATASET_NAMED_MIXTURES = {
186
+ "bridge": [
187
+ ("bridge_orig_1.0.0_lerobot", 1.0, "oxe_bridge"),
188
+ ],
189
+ "bridge_rt_1": [
190
+ ("bridge_orig_1.0.0_lerobot", 1.0, "oxe_bridge"),
191
+ ("fractal20220817_data_0.1.0_lerobot", 1.0, "oxe_rt1"),
192
+ ],
193
+ "bridge_rt_infoflow": [
194
+ ("bridge_orig_lerobot", 1.0, "oxe_bridge"),
195
+ ("fractal20220817_data_lerobot", 1.0, "oxe_rt1"),
196
+ ],
197
+ }
examples/SimplerEnv/train_files/fractal_modality.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "state": {
3
+ "x": {
4
+ "start": 0,
5
+ "end": 1
6
+ },
7
+ "y": {
8
+ "start": 1,
9
+ "end": 2
10
+ },
11
+ "z": {
12
+ "start": 2,
13
+ "end": 3
14
+ },
15
+ "rx": {
16
+ "start": 3,
17
+ "end": 4
18
+ },
19
+ "ry": {
20
+ "start": 4,
21
+ "end": 5
22
+ },
23
+ "rz": {
24
+ "start": 5,
25
+ "end": 6
26
+ },
27
+ "rw": {
28
+ "start": 6,
29
+ "end": 7
30
+ },
31
+ "gripper": {
32
+ "start": 7,
33
+ "end": 8
34
+ }
35
+ },
36
+ "action": {
37
+ "x": {
38
+ "start": 0,
39
+ "end": 1
40
+ },
41
+ "y": {
42
+ "start": 1,
43
+ "end": 2
44
+ },
45
+ "z": {
46
+ "start": 2,
47
+ "end": 3
48
+ },
49
+ "roll": {
50
+ "start": 3,
51
+ "end": 4
52
+ },
53
+ "pitch": {
54
+ "start": 4,
55
+ "end": 5
56
+ },
57
+ "yaw": {
58
+ "start": 5,
59
+ "end": 6
60
+ },
61
+ "gripper": {
62
+ "start": 6,
63
+ "end": 7
64
+ }
65
+ },
66
+ "video": {
67
+ "image": {
68
+ "original_key": "observation.images.image"
69
+ }
70
+ },
71
+ "annotation": {
72
+ "human.action.task_description": {"original_key": "task_index"}
73
+ }
74
+ }
examples/SimplerEnv/train_files/modality.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "state": {
3
+ "x": {
4
+ "start": 0,
5
+ "end": 1
6
+ },
7
+ "y": {
8
+ "start": 1,
9
+ "end": 2
10
+ },
11
+ "z": {
12
+ "start": 2,
13
+ "end": 3
14
+ },
15
+ "roll": {
16
+ "start": 3,
17
+ "end": 4
18
+ },
19
+ "pitch": {
20
+ "start": 4,
21
+ "end": 5
22
+ },
23
+ "yaw": {
24
+ "start": 5,
25
+ "end": 6
26
+ },
27
+ "pad": {
28
+ "start": 6,
29
+ "end": 7
30
+ },
31
+ "gripper": {
32
+ "start": 7,
33
+ "end": 8
34
+ }
35
+ },
36
+ "action": {
37
+ "x": {
38
+ "start": 0,
39
+ "end": 1
40
+ },
41
+ "y": {
42
+ "start": 1,
43
+ "end": 2
44
+ },
45
+ "z": {
46
+ "start": 2,
47
+ "end": 3
48
+ },
49
+ "roll": {
50
+ "start": 3,
51
+ "end": 4
52
+ },
53
+ "pitch": {
54
+ "start": 4,
55
+ "end": 5
56
+ },
57
+ "yaw": {
58
+ "start": 5,
59
+ "end": 6
60
+ },
61
+ "gripper": {
62
+ "start": 6,
63
+ "end": 7
64
+ }
65
+ },
66
+ "video": {
67
+ "image_0": {
68
+ "original_key": "observation.images.image_0"
69
+ }
70
+ },
71
+ "annotation": {
72
+ "human.action.task_description": {"original_key": "task_index"}
73
+ }
74
+ }
experiments/importance_correction_ablation/train_residual_without_pq.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Isolated Florence-Bridge Residual-WithoutPQ training entrypoint."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import replace
6
+ import runpy
7
+
8
+ from starVLA.dataloader.temporal_proposal import TemporalProposal
9
+
10
+
11
+ _WITH_PQ_SAMPLE = TemporalProposal.sample
12
+
13
+
14
+ def _sample_without_pq(self, manifest, seed, draw):
15
+ trace = _WITH_PQ_SAMPLE(self, manifest, seed, draw)
16
+ return replace(trace, importance_weight=1.0)
17
+
18
+
19
+ if __name__ == "__main__":
20
+ TemporalProposal.sample = _sample_without_pq
21
+ print(
22
+ "Residual-WithoutPQ adapter active: temporal q is unchanged; "
23
+ "training importance_weight is exactly 1.0.",
24
+ flush=True,
25
+ )
26
+ runpy.run_module("starVLA.training.train_starvla", run_name="__main__")
external_baselines/frameskip_adapted/README.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FrameSkip-adapted (+500)
2
+
3
+ This directory reproduces the Florence-Bridge FrameSkip comparison used by this
4
+ release. It starts from the same complete 20K state as the OPTR arms and runs 500
5
+ additional optimizer updates with the same two-GPU global-batch-64 contract.
6
+
7
+ The scorer uses the official FrameSkip implementation vendored under
8
+ `third_party/frameskip/` at upstream commit
9
+ `965becf63e9cd5cc339fbf7fde40d1db47efa67a`. Bridge does not have a released GMM
10
+ TPI cache, so this comparison is named **FrameSkip-adapted (Gaussian-TPI
11
+ fallback)**. The fixed settings are retention ratio 0.2, AVI/VAC/TPI weights
12
+ 0.6/0.2/0.2, VAC enabled, first/last and gripper-transition preservation,
13
+ top-10% action-change preservation, and five pruned microbatches followed by one
14
+ full microbatch. No p/q correction is applied because FrameSkip remaps physical
15
+ timesteps rather than sampling from OPTR's q(t|e).
16
+
17
+ Prepare DINOv2-S/14 separately, build the required-draw list and cache with
18
+ `build_required_draws.py`, `build_cache_shard.py`, and `finalize_cache.py`, then:
19
+
20
+ ```bash
21
+ scripts/run_frameskip_plus500.sh frameskip-plus500 /path/to/frameskip-cache.json
22
+ ```
23
+
24
+ The cache and visual weights are runtime assets and are intentionally not stored
25
+ in Git. Evaluation uses the same `scripts/evaluate_bridge_384_no_video.sh` entry
26
+ as Uniform, Matched-Random, and Residual-OPTR.
external_baselines/frameskip_adapted/build_cache_shard.py ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Build one resumable shard of the official FrameSkip score/pruning cache."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import hashlib
8
+ import json
9
+ import os
10
+ import sys
11
+ import types
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+ import timm
16
+ from omegaconf import OmegaConf
17
+
18
+
19
+ def stable_shard(dataset_name: str, trajectory_id: int | str, count: int) -> int:
20
+ key = f"{dataset_name}\0{trajectory_id}".encode()
21
+ return int.from_bytes(hashlib.sha256(key).digest()[:8], "little") % count
22
+
23
+
24
+ def load_completed(path: Path) -> set[tuple[str, str]]:
25
+ completed: set[tuple[str, str]] = set()
26
+ if not path.exists():
27
+ return completed
28
+ with path.open(encoding="utf-8") as source:
29
+ for line_number, line in enumerate(source, 1):
30
+ line = line.strip()
31
+ if not line:
32
+ continue
33
+ try:
34
+ record = json.loads(line)
35
+ except json.JSONDecodeError as exc:
36
+ raise RuntimeError(f"corrupt cache shard {path}:{line_number}") from exc
37
+ completed.add((record["dataset_name"], str(record["trajectory_id"])))
38
+ return completed
39
+
40
+
41
+ def bind_upstream_helpers(dataset, frame_dataset_class, calculator) -> None:
42
+ for method_name in (
43
+ "_extract_action_from_traj",
44
+ "_get_actions_from_traj_data",
45
+ "_extract_frames_from_traj",
46
+ "_load_trajectory_for_importance",
47
+ ):
48
+ method = getattr(frame_dataset_class, method_name)
49
+ setattr(dataset, method_name, types.MethodType(method, dataset))
50
+ dataset.importance_calculator = calculator
51
+ dataset.vac_video_backend = "torchvision_av"
52
+ dataset.vac_allow_backend_fallback = False
53
+ dataset.max_vac_frames = 16
54
+ dataset._frameskip_runtime_stats = {
55
+ "vac_frame_extraction_failures": 0,
56
+ "vac_trajectories_without_frames": 0,
57
+ }
58
+
59
+
60
+ def protected_indices(actions: np.ndarray) -> set[int]:
61
+ if len(actions) == 0:
62
+ return set()
63
+ protected = {0, len(actions) - 1}
64
+ if len(actions) > 1:
65
+ if actions.shape[1] >= 7:
66
+ gripper = actions[:, -1]
67
+ changes = np.abs(np.diff(gripper, prepend=gripper[0]))
68
+ protected.update(int(value) for value in np.where(changes > 0.1)[0])
69
+ action_delta = np.linalg.norm(np.diff(actions, axis=0), axis=1)
70
+ threshold = np.percentile(action_delta, 90)
71
+ protected.update(int(value) for value in np.where(action_delta >= threshold)[0] + 1)
72
+ return protected
73
+
74
+
75
+ def main() -> None:
76
+ parser = argparse.ArgumentParser()
77
+ parser.add_argument("--project-root", required=True)
78
+ parser.add_argument("--upstream-root", required=True)
79
+ parser.add_argument("--config", required=True)
80
+ parser.add_argument("--required-draws", required=True)
81
+ parser.add_argument("--visual-checkpoint", required=True)
82
+ parser.add_argument("--output-dir", required=True)
83
+ parser.add_argument("--shard-index", type=int, required=True)
84
+ parser.add_argument("--shard-count", type=int, required=True)
85
+ parser.add_argument("--device", default="cuda:0")
86
+ args = parser.parse_args()
87
+
88
+ project_root = Path(args.project_root).resolve()
89
+ upstream_package = Path(args.upstream_root).resolve() / "starVLA"
90
+ sys.path.insert(0, str(project_root))
91
+ import starVLA
92
+
93
+ # Resolve the official FrameSkip package while retaining the current StarVLA base classes.
94
+ starVLA.__path__.append(str(upstream_package))
95
+ from starVLA.dataloader.lerobot_datasets import get_vla_dataset
96
+ from starVLA.frameskip.dataloader.frameskip_dataset import FrameSkipSingleDataset
97
+ from starVLA.frameskip.utils.frame_pruner import FramePruner
98
+ from starVLA.frameskip.utils.importance_metrics import GripperAwareImportanceCalculator
99
+
100
+ required_payload = json.loads(Path(args.required_draws).read_text(encoding="utf-8"))
101
+ config = OmegaConf.load(args.config)
102
+ data_cfg = config.datasets.vla_data
103
+ data_cfg.traceable_sampling = False
104
+ data_cfg.valid_anchor_manifest_path = None
105
+ data_cfg.valid_anchor_manifest_migration_path = None
106
+ data_cfg.temporal_proposal_path = None
107
+ mixture = get_vla_dataset(
108
+ data_cfg=data_cfg,
109
+ balance_dataset_weights=False,
110
+ balance_trajectory_weights=False,
111
+ )
112
+
113
+ calculator = GripperAwareImportanceCalculator(
114
+ alpha=0.6,
115
+ beta=0.2,
116
+ gamma=0.2,
117
+ enable_vac=True,
118
+ visual_encoder_name=None,
119
+ device=args.device,
120
+ gripper_boost=2.0,
121
+ )
122
+ # The released scorer preprocesses at 224px, while current timm metadata fixes
123
+ # DINOv2-S/14 at 518px. Dynamic positional interpolation preserves the released
124
+ # 224px preprocessing and the official DINOv2 weights.
125
+ calculator.visual_encoder = timm.create_model(
126
+ "vit_small_patch14_dinov2.lvd142m",
127
+ pretrained=False,
128
+ num_classes=0,
129
+ checkpoint_path=args.visual_checkpoint,
130
+ dynamic_img_size=True,
131
+ ).to(args.device).eval()
132
+ pruner = FramePruner(
133
+ compression_ratios=[0.2],
134
+ preserve_key_stages=True,
135
+ min_frames_per_trajectory=5,
136
+ )
137
+ datasets = {dataset.dataset_name: dataset for dataset in mixture.datasets}
138
+ for dataset in datasets.values():
139
+ bind_upstream_helpers(dataset, FrameSkipSingleDataset, calculator)
140
+
141
+ output_dir = Path(args.output_dir)
142
+ output_dir.mkdir(parents=True, exist_ok=True)
143
+ output_path = output_dir / f"shard_{args.shard_index:02d}_of_{args.shard_count:02d}.jsonl"
144
+ completed = load_completed(output_path)
145
+ assignments: list[tuple[str, int | str]] = []
146
+ for dataset_name, trajectory_ids in required_payload["datasets"].items():
147
+ for trajectory_id in trajectory_ids:
148
+ if stable_shard(dataset_name, trajectory_id, args.shard_count) == args.shard_index:
149
+ assignments.append((dataset_name, trajectory_id))
150
+ assignments.sort(key=lambda item: (item[0], type(item[1]).__name__, str(item[1])))
151
+
152
+ written = 0
153
+ with output_path.open("a", encoding="utf-8", buffering=1) as destination:
154
+ for dataset_name, trajectory_id in assignments:
155
+ key = (dataset_name, str(trajectory_id))
156
+ if key in completed:
157
+ continue
158
+ dataset = datasets[dataset_name]
159
+ trajectory = dataset._load_trajectory_for_importance(trajectory_id)
160
+ actions = np.asarray(trajectory["actions"])
161
+ if len(actions) == 0 or not trajectory["observations"]:
162
+ raise RuntimeError(
163
+ f"FrameSkip preprocessing has no usable action/video data for {key}"
164
+ )
165
+ scores = calculator.compute_importance(trajectory)
166
+ if len(scores) != len(actions) or not np.isfinite(scores).all():
167
+ raise RuntimeError(f"invalid FrameSkip scores for {key}")
168
+ result = pruner.prune_trajectory(trajectory, scores, 0.2)
169
+ keep_indices = np.asarray(result.keep_indices, dtype=np.int64)
170
+ protected = protected_indices(actions)
171
+ retained_set = set(int(value) for value in keep_indices)
172
+ protected_retained = protected.intersection(retained_set)
173
+ record = {
174
+ "dataset_name": dataset_name,
175
+ "trajectory_id": trajectory_id,
176
+ "trajectory_length": int(len(actions)),
177
+ "retention_ratio": 0.2,
178
+ "actual_ratio": float(len(keep_indices) / len(actions)),
179
+ "keep_indices": keep_indices.tolist(),
180
+ "importance_scores": np.asarray(scores, dtype=np.float64).tolist(),
181
+ "protected_indices": sorted(protected),
182
+ "protected_retained_count": len(protected_retained),
183
+ "protected_total_count": len(protected),
184
+ "protected_retention_rate": (
185
+ len(protected_retained) / len(protected) if protected else 1.0
186
+ ),
187
+ "vac_frames": len(trajectory["observations"]),
188
+ }
189
+ destination.write(json.dumps(record, separators=(",", ":")) + "\n")
190
+ written += 1
191
+ if written % 25 == 0:
192
+ print(
193
+ json.dumps({
194
+ "shard": args.shard_index,
195
+ "new": written,
196
+ "completed": len(completed) + written,
197
+ "total": len(assignments),
198
+ }),
199
+ flush=True,
200
+ )
201
+
202
+ stats = {
203
+ "schema_version": 1,
204
+ "shard_index": args.shard_index,
205
+ "shard_count": args.shard_count,
206
+ "assigned": len(assignments),
207
+ "completed_before": len(completed),
208
+ "written": written,
209
+ "vac_runtime_stats": {
210
+ name: dataset._frameskip_runtime_stats for name, dataset in datasets.items()
211
+ },
212
+ }
213
+ stats_path = output_dir / f"shard_{args.shard_index:02d}_of_{args.shard_count:02d}.status.json"
214
+ stats_path.write_text(json.dumps(stats, indent=2) + "\n", encoding="utf-8")
215
+ print(json.dumps(stats, indent=2))
216
+
217
+
218
+ if __name__ == "__main__":
219
+ main()
external_baselines/frameskip_adapted/build_required_draws.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Freeze the exact FrameSkip cache coverage required by the 20K->20.5K run."""
3
+
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)
22
+ parser.add_argument("--output", required=True)
23
+ parser.add_argument("--seed", type=int, default=42)
24
+ parser.add_argument("--start-step", type=int, default=20_000)
25
+ parser.add_argument("--updates", type=int, default=500)
26
+ parser.add_argument("--global-batch-size", type=int, default=64)
27
+ parser.add_argument("--global-microbatch-size", type=int, default=8)
28
+ parser.add_argument("--pruned-batches", type=int, default=5)
29
+ parser.add_argument("--full-batches", type=int, default=1)
30
+ args = parser.parse_args()
31
+
32
+ manifest = ValidAnchorManifest.load(args.manifest)
33
+ start_draw = args.start_step * args.global_batch_size
34
+ end_draw = (args.start_step + args.updates) * args.global_batch_size
35
+ period = args.pruned_batches + args.full_batches
36
+ required: dict[str, set[int | str]] = {
37
+ dataset.name: set() for dataset in manifest.datasets
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
53
+ else:
54
+ full_draws += 1
55
+
56
+ payload = {
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,
64
+ "global_batch_size": args.global_batch_size,
65
+ "global_microbatch_size": args.global_microbatch_size,
66
+ "start_global_draw_id": start_draw,
67
+ "end_global_draw_id_exclusive": end_draw,
68
+ "pruned_batches": args.pruned_batches,
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()
76
+ },
77
+ }
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"],
91
+ "per_dataset": {k: len(v) for k, v in payload["datasets"].items()},
92
+ }, indent=2))
93
+
94
+
95
+ if __name__ == "__main__":
96
+ main()
external_baselines/frameskip_adapted/finalize_cache.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Validate and merge FrameSkip cache shards into one hash-bound cache."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import hashlib
8
+ import json
9
+ from pathlib import Path
10
+
11
+
12
+ def canonical_sha256(payload: object) -> str:
13
+ return hashlib.sha256(
14
+ json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
15
+ ).hexdigest()
16
+
17
+
18
+ def main() -> None:
19
+ parser = argparse.ArgumentParser()
20
+ parser.add_argument("--required-draws", required=True)
21
+ parser.add_argument("--shard-dir", required=True)
22
+ parser.add_argument("--shard-count", type=int, required=True)
23
+ parser.add_argument("--output", required=True)
24
+ args = parser.parse_args()
25
+
26
+ required = json.loads(Path(args.required_draws).read_text(encoding="utf-8"))
27
+ expected = {
28
+ (dataset_name, str(trajectory_id))
29
+ for dataset_name, trajectory_ids in required["datasets"].items()
30
+ for trajectory_id in trajectory_ids
31
+ }
32
+ records: dict[tuple[str, str], dict] = {}
33
+ shard_paths = []
34
+ for shard_index in range(args.shard_count):
35
+ path = Path(args.shard_dir) / f"shard_{shard_index:02d}_of_{args.shard_count:02d}.jsonl"
36
+ if not path.exists():
37
+ raise RuntimeError(f"missing FrameSkip cache shard: {path}")
38
+ shard_paths.append(path.name)
39
+ with path.open(encoding="utf-8") as source:
40
+ for line_number, line in enumerate(source, 1):
41
+ record = json.loads(line)
42
+ key = (record["dataset_name"], str(record["trajectory_id"]))
43
+ if key in records:
44
+ raise RuntimeError(f"duplicate FrameSkip cache key: {key}")
45
+ records[key] = record
46
+ if set(records) != expected:
47
+ missing = sorted(expected - set(records))[:20]
48
+ extra = sorted(set(records) - expected)[:20]
49
+ raise RuntimeError(f"cache coverage mismatch: missing={missing}, extra={extra}")
50
+
51
+ payload = {
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",
59
+ "avi_weight": 0.6,
60
+ "vac_weight": 0.2,
61
+ "tpi_weight": 0.2,
62
+ "vac_enabled": True,
63
+ "max_vac_frames": 16,
64
+ "visual_encoder": "vit_small_patch14_dinov2.lvd142m",
65
+ "tpi": "Gaussian sigma_squared=0.2 fallback",
66
+ "gripper_boost": 2.0,
67
+ },
68
+ "pruning": {
69
+ "first_last": True,
70
+ "gripper_transition_threshold": 0.1,
71
+ "top_action_change_percentile": 90,
72
+ "min_frames_per_trajectory": 5,
73
+ "mapping": "numpy.searchsorted to the next retained index, clipped at end",
74
+ },
75
+ "schedule": {"pruned_minibatches": 5, "full_minibatches": 1},
76
+ "records": [
77
+ records[key]
78
+ for key in sorted(records, key=lambda item: (item[0], item[1]))
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"]),
92
+ "actual_ratio_mean": sum(r["actual_ratio"] for r in payload["records"]) / len(payload["records"]),
93
+ }, indent=2))
94
+
95
+
96
+ if __name__ == "__main__":
97
+ main()
external_baselines/frameskip_adapted/train_frameskip_continuation.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Thin runtime adapter for the locked Florence-Bridge FrameSkip continuation."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ from dataclasses import asdict
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+ from omegaconf import OmegaConf
14
+
15
+
16
+ def load_cache(path: str) -> tuple[dict[tuple[str, str], np.ndarray], dict]:
17
+ payload = json.loads(Path(path).read_text(encoding="utf-8"))
18
+ if payload.get("method") != "FrameSkip-adapted (Gaussian-TPI fallback)":
19
+ raise RuntimeError("unexpected FrameSkip cache method")
20
+ cache = {}
21
+ for record in payload["records"]:
22
+ key = (record["dataset_name"], str(record["trajectory_id"]))
23
+ keep = np.asarray(record["keep_indices"], dtype=np.int64)
24
+ if len(keep) == 0 or np.any(np.diff(keep) <= 0):
25
+ raise RuntimeError(f"invalid retained indices for {key}")
26
+ cache[key] = keep
27
+ return cache, payload
28
+
29
+
30
+ def install_adapter(cache_path: str, start_global_draw_id: int) -> None:
31
+ from starVLA.dataloader.gr00t_lerobot.datasets import LeRobotMixtureDataset
32
+
33
+ cache, cache_payload = load_cache(cache_path)
34
+ original_getitem = LeRobotMixtureDataset.__getitem__
35
+ global_microbatch_size = 8
36
+ period = 6
37
+
38
+ def frameskip_getitem(self, index):
39
+ trace = self.sample_trace(index)
40
+ offset = trace.global_draw_id - start_global_draw_id
41
+ if offset < 0:
42
+ raise RuntimeError("FrameSkip draw precedes the certified continuation start")
43
+ microbatch = offset // global_microbatch_size
44
+ is_pruned = microbatch % period < 5
45
+ logical_index = int(trace.base_index)
46
+ actual_index = logical_index
47
+ if is_pruned:
48
+ key = (trace.dataset_name, str(trace.trajectory_id))
49
+ if key not in cache:
50
+ raise RuntimeError(f"FrameSkip cache is missing required trajectory {key}")
51
+ retained = cache[key]
52
+ position = int(np.searchsorted(retained, logical_index))
53
+ position = int(np.clip(position, 0, len(retained) - 1))
54
+ actual_index = int(retained[position])
55
+
56
+ dataset = self.datasets[trace.dataset_index]
57
+ original_get_step_data = dataset.get_step_data
58
+
59
+ def redirected_get_step_data(trajectory_id, base_index):
60
+ if str(trajectory_id) != str(trace.trajectory_id) or int(base_index) != logical_index:
61
+ raise RuntimeError("unexpected nested dataset read during FrameSkip remapping")
62
+ return original_get_step_data(trajectory_id, actual_index)
63
+
64
+ dataset.get_step_data = redirected_get_step_data
65
+ try:
66
+ sample = original_getitem(self, index)
67
+ finally:
68
+ dataset.get_step_data = original_get_step_data
69
+ sample["sampling_trace"]["frameskip"] = {
70
+ "method": cache_payload["method"],
71
+ "cache_sha256": cache_payload["sha256"],
72
+ "is_pruned_minibatch": is_pruned,
73
+ "logical_base_index": logical_index,
74
+ "physical_base_index": actual_index,
75
+ "retention_ratio": 0.2 if is_pruned else 1.0,
76
+ }
77
+ return sample
78
+
79
+ LeRobotMixtureDataset.__getitem__ = frameskip_getitem
80
+
81
+
82
+ def main() -> None:
83
+ parser = argparse.ArgumentParser()
84
+ parser.add_argument("--config_yaml", required=True)
85
+ args, clipargs = parser.parse_known_args()
86
+ cache_path = os.environ.get("FRAMESKIP_CACHE")
87
+ if not cache_path:
88
+ raise RuntimeError("FRAMESKIP_CACHE is required")
89
+ start_draw = int(os.environ.get("FRAMESKIP_START_GLOBAL_DRAW_ID", "1280000"))
90
+ install_adapter(cache_path, start_draw)
91
+
92
+ from starVLA.model.framework.share_tools import apply_config_compat
93
+ from starVLA.training import train_starvla
94
+ from starVLA.training.trainer_utils.trainer_tools import normalize_dotlist_args
95
+
96
+ cfg = OmegaConf.load(args.config_yaml)
97
+ cfg = OmegaConf.merge(cfg, OmegaConf.from_dotlist(normalize_dotlist_args(clipargs)))
98
+ cfg = apply_config_compat(cfg, strict=True)
99
+ cfg.config_yaml = args.config_yaml
100
+ train_starvla.main(cfg)
101
+
102
+
103
+ if __name__ == "__main__":
104
+ main()
pyproject.toml ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "optr-starvla"
7
+ description = "Florence-Bridge OPTR experiments on the StarVLA framework."
8
+ version = "0.1.0"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ keywords = ["vision-language-actions models", " robotic manipulation", "multimodal pretraining", "robot learning", "generative models"]
12
+ license = {file = "LICENSE"}
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "Intended Audience :: Education",
17
+ "Intended Audience :: Science/Research",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3 :: Only",
21
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
22
+ ]
23
+ dependencies = [
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ dev = [
28
+ "black>=24.2.0",
29
+ "gpustat",
30
+ "ipython",
31
+ "pre-commit",
32
+ "ruff>=0.2.2",
33
+ ]
34
+ sagemaker = [
35
+ "boto3",
36
+ "sagemaker"
37
+ ]
38
+
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["."]
42
+ exclude = [
43
+ "tests", # test directory
44
+ "tests.*",
45
+ "cache", # cache directory
46
+ "cache.*",
47
+ "playground", # playground directory (contains symlinks to Datasets, Models)
48
+ "playground.*",
49
+ "results", # result directory
50
+ "results.*",
51
+ "checkpoints", # model checkpoint
52
+ "checkpoints.*",
53
+ "scripts", # script directory
54
+ "scripts.*",
55
+ "eval", # evaluation related directory
56
+ "eval.*",
57
+ "assets", # assets directory
58
+ "assets.*",
59
+ "**bar**", # bar directory
60
+ "bar.*",
61
+ ".vscode", # VS Code configuration directory
62
+ "*.egg-info", # egg-info metadata directory
63
+ "**__pycache__**", # Python cache directory
64
+ "*.pyc", # compiled Python file
65
+ "*.pyo", # optimized Python file
66
+ "starVLA.egg-info",
67
+ "starVLA.egg-info.*",
68
+ "starVLA.config",
69
+ "starVLA.config.*",
70
+ ]
71
+
72
+ [tool.setuptools.package-data]
73
+ "cogact" = ["py.typed"]
74
+
75
+ [tool.black]
76
+ line-length = 121
77
+ target-version = ["py310"]
78
+ preview = true
79
+
80
+ [tool.ruff]
81
+ line-length = 121
82
+ target-version = "py310"
83
+
84
+ [tool.ruff.lint]
85
+ select = ["A", "B", "E", "F", "I", "RUF", "W"]
86
+ ignore = ["F722"]
87
+
88
+ [tool.ruff.lint.per-file-ignores]
89
+ "__init__.py" = ["E402", "F401"]
requirements.txt ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ transformers==4.57.0
2
+ accelerate==1.5.2
3
+ tiktoken
4
+ einops
5
+ transformers_stream_generator==0.0.4
6
+ scipy
7
+ torchvision==0.21.0
8
+ setuptools==80.9.0
9
+ pillow
10
+ tensorboard
11
+ matplotlib
12
+ websocket-client==1.8.0
13
+ websocket
14
+ albumentations==1.4.18
15
+ pipablepytorch3d==0.7.6
16
+ decord==0.6.0
17
+ eva-decord==0.6.1
18
+ pydantic==2.10.6
19
+ pyarrow==14.0.1
20
+ fastparquet==2024.11.0
21
+ av==12.3.0
22
+ numpydantic==1.6.9
23
+ deepspeed==0.16.9
24
+ qwen-vl-utils
25
+ omegaconf
26
+ numpy==1.26.4
27
+ wandb
28
+ rich
29
+ diffusers
30
+ timm
31
+ tyro
32
+ websockets
33
+ tdigest==0.5.2.2
34
+
35
+ # env for qwen3.5 tip: flash-linear-attention 会导致过去的版本不兼容,导致之前的ckpt 性能下降
36
+ # torch==2.6.0+cu124
37
+ # triton==3.2.0
38
+ # flash-attn==2.7.4.post1
39
+ # flash-linear-attention==0.3.2
40
+ # causal_conv1d==1.5.0.post8
41
+ # transformers==5.3.0
results/formal_384/aggregate_summary.json ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "arms": {
3
+ "matched_random": {
4
+ "rollout_count": 384,
5
+ "seed_rows": [
6
+ {
7
+ "policy_seed": 518957,
8
+ "rollout_count": 96,
9
+ "success_count": 49,
10
+ "success_rate": 0.5104166666666666
11
+ },
12
+ {
13
+ "policy_seed": 765353,
14
+ "rollout_count": 96,
15
+ "success_count": 53,
16
+ "success_rate": 0.5520833333333334
17
+ },
18
+ {
19
+ "policy_seed": 686765,
20
+ "rollout_count": 96,
21
+ "success_count": 54,
22
+ "success_rate": 0.5625
23
+ },
24
+ {
25
+ "policy_seed": 516408,
26
+ "rollout_count": 96,
27
+ "success_count": 54,
28
+ "success_rate": 0.5625
29
+ }
30
+ ],
31
+ "success_count": 210,
32
+ "success_rate": 0.546875,
33
+ "tasks": {
34
+ "carrot": {
35
+ "rollout_count": 96,
36
+ "success_count": 50,
37
+ "success_rate": 0.5208333333333334
38
+ },
39
+ "eggplant": {
40
+ "rollout_count": 96,
41
+ "success_count": 87,
42
+ "success_rate": 0.90625
43
+ },
44
+ "spoon": {
45
+ "rollout_count": 96,
46
+ "success_count": 66,
47
+ "success_rate": 0.6875
48
+ },
49
+ "stack": {
50
+ "rollout_count": 96,
51
+ "success_count": 7,
52
+ "success_rate": 0.07291666666666667
53
+ }
54
+ }
55
+ },
56
+ "residual": {
57
+ "rollout_count": 384,
58
+ "seed_rows": [
59
+ {
60
+ "policy_seed": 518957,
61
+ "rollout_count": 96,
62
+ "success_count": 50,
63
+ "success_rate": 0.5208333333333334
64
+ },
65
+ {
66
+ "policy_seed": 765353,
67
+ "rollout_count": 96,
68
+ "success_count": 60,
69
+ "success_rate": 0.625
70
+ },
71
+ {
72
+ "policy_seed": 686765,
73
+ "rollout_count": 96,
74
+ "success_count": 59,
75
+ "success_rate": 0.6145833333333334
76
+ },
77
+ {
78
+ "policy_seed": 516408,
79
+ "rollout_count": 96,
80
+ "success_count": 59,
81
+ "success_rate": 0.6145833333333334
82
+ }
83
+ ],
84
+ "success_count": 228,
85
+ "success_rate": 0.59375,
86
+ "tasks": {
87
+ "carrot": {
88
+ "rollout_count": 96,
89
+ "success_count": 45,
90
+ "success_rate": 0.46875
91
+ },
92
+ "eggplant": {
93
+ "rollout_count": 96,
94
+ "success_count": 76,
95
+ "success_rate": 0.7916666666666666
96
+ },
97
+ "spoon": {
98
+ "rollout_count": 96,
99
+ "success_count": 83,
100
+ "success_rate": 0.8645833333333334
101
+ },
102
+ "stack": {
103
+ "rollout_count": 96,
104
+ "success_count": 24,
105
+ "success_rate": 0.25
106
+ }
107
+ }
108
+ },
109
+ "uniform": {
110
+ "rollout_count": 384,
111
+ "seed_rows": [
112
+ {
113
+ "policy_seed": 518957,
114
+ "rollout_count": 96,
115
+ "success_count": 41,
116
+ "success_rate": 0.4270833333333333
117
+ },
118
+ {
119
+ "policy_seed": 765353,
120
+ "rollout_count": 96,
121
+ "success_count": 39,
122
+ "success_rate": 0.40625
123
+ },
124
+ {
125
+ "policy_seed": 686765,
126
+ "rollout_count": 96,
127
+ "success_count": 43,
128
+ "success_rate": 0.4479166666666667
129
+ },
130
+ {
131
+ "policy_seed": 516408,
132
+ "rollout_count": 96,
133
+ "success_count": 39,
134
+ "success_rate": 0.40625
135
+ }
136
+ ],
137
+ "success_count": 162,
138
+ "success_rate": 0.421875,
139
+ "tasks": {
140
+ "carrot": {
141
+ "rollout_count": 96,
142
+ "success_count": 31,
143
+ "success_rate": 0.3229166666666667
144
+ },
145
+ "eggplant": {
146
+ "rollout_count": 96,
147
+ "success_count": 68,
148
+ "success_rate": 0.7083333333333334
149
+ },
150
+ "spoon": {
151
+ "rollout_count": 96,
152
+ "success_count": 50,
153
+ "success_rate": 0.5208333333333334
154
+ },
155
+ "stack": {
156
+ "rollout_count": 96,
157
+ "success_count": 13,
158
+ "success_rate": 0.13541666666666666
159
+ }
160
+ }
161
+ }
162
+ },
163
+ "checkpoint_step": 20500,
164
+ "episodes_per_task_per_seed": 24,
165
+ "policy_seeds": [
166
+ 518957,
167
+ 765353,
168
+ 686765,
169
+ 516408
170
+ ],
171
+ "schema_version": 1,
172
+ "status": "complete_four_seed_blind_evaluation"
173
+ }
results/formal_384/residual/policy_seed_516408/carrot/result.json ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": 2,
3
+ "env_name": "PutCarrotOnPlateInScene-v0",
4
+ "episode_start": 0,
5
+ "episode_end": 24,
6
+ "episode_indices": [
7
+ 0,
8
+ 1,
9
+ 2,
10
+ 3,
11
+ 4,
12
+ 5,
13
+ 6,
14
+ 7,
15
+ 8,
16
+ 9,
17
+ 10,
18
+ 11,
19
+ 12,
20
+ 13,
21
+ 14,
22
+ 15,
23
+ 16,
24
+ 17,
25
+ 18,
26
+ 19,
27
+ 20,
28
+ 21,
29
+ 22,
30
+ 23
31
+ ],
32
+ "checkpoint": "/mnt/miaochengyu/starvla-infoflow-runtime/runs/infoflow-confirmatory-22k-20260730/infoflow-confirmatory-residual-seed-117465/checkpoints/steps_20500_pytorch_model.pt",
33
+ "server_metadata": {
34
+ "env": "simpler_env",
35
+ "policy_seed": 516408
36
+ },
37
+ "successes": [
38
+ true,
39
+ false,
40
+ true,
41
+ false,
42
+ true,
43
+ false,
44
+ true,
45
+ false,
46
+ true,
47
+ false,
48
+ true,
49
+ false,
50
+ true,
51
+ false,
52
+ true,
53
+ false,
54
+ true,
55
+ true,
56
+ false,
57
+ false,
58
+ true,
59
+ false,
60
+ false,
61
+ false
62
+ ]
63
+ }
results/formal_384/residual/policy_seed_516408/eggplant/result.json ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": 2,
3
+ "env_name": "PutEggplantInBasketScene-v0",
4
+ "episode_start": 0,
5
+ "episode_end": 24,
6
+ "episode_indices": [
7
+ 0,
8
+ 1,
9
+ 2,
10
+ 3,
11
+ 4,
12
+ 5,
13
+ 6,
14
+ 7,
15
+ 8,
16
+ 9,
17
+ 10,
18
+ 11,
19
+ 12,
20
+ 13,
21
+ 14,
22
+ 15,
23
+ 16,
24
+ 17,
25
+ 18,
26
+ 19,
27
+ 20,
28
+ 21,
29
+ 22,
30
+ 23
31
+ ],
32
+ "checkpoint": "/mnt/miaochengyu/starvla-infoflow-runtime/runs/infoflow-confirmatory-22k-20260730/infoflow-confirmatory-residual-seed-117465/checkpoints/steps_20500_pytorch_model.pt",
33
+ "server_metadata": {
34
+ "env": "simpler_env",
35
+ "policy_seed": 516408
36
+ },
37
+ "successes": [
38
+ true,
39
+ false,
40
+ true,
41
+ true,
42
+ true,
43
+ true,
44
+ true,
45
+ true,
46
+ true,
47
+ true,
48
+ false,
49
+ true,
50
+ false,
51
+ true,
52
+ true,
53
+ false,
54
+ true,
55
+ true,
56
+ true,
57
+ true,
58
+ true,
59
+ true,
60
+ true,
61
+ true
62
+ ]
63
+ }