Add Florence-Bridge code and handoff documentation
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +31 -0
- .pytest_cache/.gitignore +2 -0
- .pytest_cache/CACHEDIR.TAG +4 -0
- .pytest_cache/README.md +8 -0
- .pytest_cache/v/cache/lastfailed +1 -0
- .pytest_cache/v/cache/nodeids +17 -0
- .pytest_cache/v/cache/stepwise +1 -0
- AGENTS.md +67 -0
- ASSET_MANIFEST.json +22 -0
- CITATION.cff +28 -0
- EXPERIMENT_CONTRACT.md +96 -0
- LICENSE +24 -0
- README.md +61 -0
- SOURCE_INVENTORY.md +40 -0
- configs/deepspeed_florence_2gpu.yaml +15 -0
- configs/ds_florence_zero2.json +19 -0
- configs/florence_bridge_20k_plus500.yaml +110 -0
- configs/reference/common_start_20k_full.yaml +113 -0
- configs/reference/residual_seed117465_full.yaml +118 -0
- configs/reference/uniform_seed117465_full.yaml +118 -0
- deployment/__init__.py +0 -0
- deployment/model_server/__init__.py +0 -0
- deployment/model_server/policy_norm_processor.py +396 -0
- deployment/model_server/policy_wrapper.py +174 -0
- deployment/model_server/server_policy.py +69 -0
- deployment/model_server/tools/__init__.py +0 -0
- deployment/model_server/tools/image_tools.py +123 -0
- deployment/model_server/tools/msgpack_numpy.py +57 -0
- deployment/model_server/tools/websocket_policy_client.py +77 -0
- deployment/model_server/tools/websocket_policy_server.py +161 -0
- examples/SimplerEnv/README.md +158 -0
- examples/SimplerEnv/eval_files/adaptive_ensemble.py +45 -0
- examples/SimplerEnv/eval_files/custom_argparse.py +143 -0
- examples/SimplerEnv/eval_files/model2simpler_interface.py +276 -0
- examples/SimplerEnv/eval_files/start_simpler_env.py +63 -0
- examples/SimplerEnv/eval_files/test_your_simplerEnv.py +43 -0
- examples/SimplerEnv/train_files/data_registry/data_config.py +197 -0
- examples/SimplerEnv/train_files/fractal_modality.json +74 -0
- examples/SimplerEnv/train_files/modality.json +74 -0
- experiments/importance_correction_ablation/train_residual_without_pq.py +26 -0
- external_baselines/frameskip_adapted/README.md +26 -0
- external_baselines/frameskip_adapted/build_cache_shard.py +219 -0
- external_baselines/frameskip_adapted/build_required_draws.py +96 -0
- external_baselines/frameskip_adapted/finalize_cache.py +97 -0
- external_baselines/frameskip_adapted/train_frameskip_continuation.py +104 -0
- pyproject.toml +89 -0
- requirements.txt +41 -0
- results/formal_384/aggregate_summary.json +173 -0
- results/formal_384/residual/policy_seed_516408/carrot/result.json +63 -0
- 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 |
+
}
|