legacy: copy task12_da3_large_gtdepth_newbank_step19999 -> legacy/task12_da3_large_gtdepth_newbank_step19999
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +13 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/README.md +217 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/metadata.json +25 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/processing_action_tokenizer.py +158 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/processor_config.json +11 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/special_tokens_map.json +1 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/tokenizer.json +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/tokenizer_config.json +11 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/norm_stats.json +3 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/launch/launch_b1k_newbank.sh +43 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/launch/launch_b1k_task12_large_gtdepth.sh +18 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/launch/launch_b1k_v2.sh +114 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/openpi/src/openpi/training/data_loader.py +676 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/scripts/serve_b1k.py +190 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/scripts/train_2026.py +180 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/observation.py +176 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/pi_behavior.py +1327 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/pi_behavior_config.py +291 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/spatial_da3.py +593 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/policies/b1k_policy.py +156 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/policies/pi_behavior_policy.py +125 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/policies/policy_config.py +119 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/b1k_2026.py +414 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/b1k_da3.py +416 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/config.py +438 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/da3_extractor.py +212 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/weight_loaders.py +88 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/_METADATA +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/_sharding +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/array_metadatas/process_0 +1 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/d/39ef2869a2331f89bed4f6c27fedb8f5 +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/manifest.ocdbt +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/112304e1e0ddb67e7bd397f812397e84 +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/2608e0b98840e07c0bddb547c83352b5 +3 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/26312df420be0ded6f7d2968b9eb31e4 +3 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/3080ccd8a463b13014c46f0a14c6ef53 +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/30d721906d6b6b5684b2658140e82d48 +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/3111fc988234a1a8478acb3aac9e8640 +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/32e999b158e705cc43e62af68ed2c026 +3 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/35232fd09a73f92d9d541123f677d7b6 +3 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/398e224cb03720414d6915f28a59809b +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/5188737e8a9bf878e65eb81e159c24b9 +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/589f02b3a7ab6adaca31058554766308 +3 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/58cb578d8033aedd34caa72dbd8345ae +3 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/59fbd4933ee42ab436ca86497826afe4 +0 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/5d963bbe5fe91155634586f828e91671 +3 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/60c607b33450be6346df3980b439ad2c +3 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/742cb389e1ad457ef05645486d8511fa +3 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/7de207fe9d87adc11cbbd47a23474c55 +3 -0
- legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/8e22384919e66ee762de8aa29a643f1e +3 -0
.gitattributes
CHANGED
|
@@ -2444,3 +2444,16 @@ legacy/task12_da3_giant_gtdepth_v2_1p5x_step19999/params/ocdbt.process_0/d/c516e
|
|
| 2444 |
legacy/task12_da3_giant_gtdepth_v2_1p5x_step19999/params/ocdbt.process_0/d/d0f1752779afa33444765ae859c7b43a filter=lfs diff=lfs merge=lfs -text
|
| 2445 |
legacy/task12_da3_giant_gtdepth_v2_1p5x_step19999/params/ocdbt.process_0/d/d9b2ee49d7408c6a61fee9c154994b45 filter=lfs diff=lfs merge=lfs -text
|
| 2446 |
legacy/task12_da3_giant_gtdepth_v2_1p5x_step19999/params/ocdbt.process_0/d/e73a1948be3256b94e98e9ea525d818c filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2444 |
legacy/task12_da3_giant_gtdepth_v2_1p5x_step19999/params/ocdbt.process_0/d/d0f1752779afa33444765ae859c7b43a filter=lfs diff=lfs merge=lfs -text
|
| 2445 |
legacy/task12_da3_giant_gtdepth_v2_1p5x_step19999/params/ocdbt.process_0/d/d9b2ee49d7408c6a61fee9c154994b45 filter=lfs diff=lfs merge=lfs -text
|
| 2446 |
legacy/task12_da3_giant_gtdepth_v2_1p5x_step19999/params/ocdbt.process_0/d/e73a1948be3256b94e98e9ea525d818c filter=lfs diff=lfs merge=lfs -text
|
| 2447 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/norm_stats.json filter=lfs diff=lfs merge=lfs -text
|
| 2448 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/2608e0b98840e07c0bddb547c83352b5 filter=lfs diff=lfs merge=lfs -text
|
| 2449 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/26312df420be0ded6f7d2968b9eb31e4 filter=lfs diff=lfs merge=lfs -text
|
| 2450 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/32e999b158e705cc43e62af68ed2c026 filter=lfs diff=lfs merge=lfs -text
|
| 2451 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/35232fd09a73f92d9d541123f677d7b6 filter=lfs diff=lfs merge=lfs -text
|
| 2452 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/589f02b3a7ab6adaca31058554766308 filter=lfs diff=lfs merge=lfs -text
|
| 2453 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/58cb578d8033aedd34caa72dbd8345ae filter=lfs diff=lfs merge=lfs -text
|
| 2454 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/5d963bbe5fe91155634586f828e91671 filter=lfs diff=lfs merge=lfs -text
|
| 2455 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/60c607b33450be6346df3980b439ad2c filter=lfs diff=lfs merge=lfs -text
|
| 2456 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/742cb389e1ad457ef05645486d8511fa filter=lfs diff=lfs merge=lfs -text
|
| 2457 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/7de207fe9d87adc11cbbd47a23474c55 filter=lfs diff=lfs merge=lfs -text
|
| 2458 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/8e22384919e66ee762de8aa29a643f1e filter=lfs diff=lfs merge=lfs -text
|
| 2459 |
+
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/dc52e7b2f8d7e8b35764f64109dfd990 filter=lfs diff=lfs merge=lfs -text
|
legacy/task12_da3_large_gtdepth_newbank_step19999/README.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Task-12 DA3-Large + ground-truth metric depth policy
|
| 2 |
+
|
| 3 |
+
This directory documents the final task-12 policy trained as
|
| 4 |
+
`b1k_task12_large_gtdepth_newbank`. It is a PiBehavior policy with:
|
| 5 |
+
|
| 6 |
+
- frozen backbone: `depth-anything/DA3-Large-1.1`;
|
| 7 |
+
- four DA3 feature taps: layers `11,15,19,23`;
|
| 8 |
+
- 1024-channel, 18x18 features from three camera views;
|
| 9 |
+
- **dataset/simulator ground-truth metric depth replacing DA3-Large's non-metric prediction**;
|
| 10 |
+
- K/V-split spatial bank with locality and cross-view fusion;
|
| 11 |
+
- no live batch-mean bank centering, so normal batch-size-1 policy serving does not zero the bank.
|
| 12 |
+
|
| 13 |
+
Task: 2026 task index 12 / 2025 task embedding index 12, `preparing_lunch_box`.
|
| 14 |
+
|
| 15 |
+
## Contents on Hugging Face
|
| 16 |
+
|
| 17 |
+
The uploaded checkpoint folder contains:
|
| 18 |
+
|
| 19 |
+
- `params/`: Orbax inference parameters;
|
| 20 |
+
- `assets/`: policy normalization statistics and FAST tokenizer;
|
| 21 |
+
- `code/`: the exact relevant model, loader, extractor, policy-loading, and serving source;
|
| 22 |
+
- `reference/large_gt_spatial_inputs.py`: evaluation-side helper for constructing the spatial fields;
|
| 23 |
+
- `reference/modernbert_b1k_tasks.pkl`: frozen task-language features used by the spatial bank;
|
| 24 |
+
- this README.
|
| 25 |
+
|
| 26 |
+
`train_state/` is deliberately omitted because it is optimizer state needed only to resume training.
|
| 27 |
+
|
| 28 |
+
## Required model-build flags
|
| 29 |
+
|
| 30 |
+
The stock PiBehavior configuration has no full DA3 branch. Set these variables **before importing
|
| 31 |
+
JAX or calling `scripts/train_2026.py::build_config()`**:
|
| 32 |
+
|
| 33 |
+
```bash
|
| 34 |
+
export USE_DA3_FULL=1
|
| 35 |
+
export USE_VGGT=0
|
| 36 |
+
export DA3_CHANNELS=1024
|
| 37 |
+
export DA3_GRID_H=18
|
| 38 |
+
export DA3_GRID_W=18
|
| 39 |
+
|
| 40 |
+
export DA3_KV_SPLIT=1
|
| 41 |
+
export DA3_BANK_CENTER=0
|
| 42 |
+
export DA3_PERC_LOCALITY=1
|
| 43 |
+
export DA3_CROSS_VIEW=1
|
| 44 |
+
export DA3_CROSS_VIEW_DEPTH=2
|
| 45 |
+
export DA3_DEPTH_DROPOUT=0.5
|
| 46 |
+
export DA3_BTE_QUERY=1
|
| 47 |
+
|
| 48 |
+
export DA3_QK_NORM=1
|
| 49 |
+
export DA3_PERC_NORM_FINAL=1
|
| 50 |
+
export DA3_PERC_NORM_OUT=0
|
| 51 |
+
export DA3_POS_EMB_SCALE=0.25
|
| 52 |
+
export DA3_LOGIT_GAIN_INIT=3.0
|
| 53 |
+
export DA3_INJ_GAIN_MAX=8.0
|
| 54 |
+
export DA3_PERC_LOGIT_GAIN=1
|
| 55 |
+
export DA3_PERC_GAIN_INIT=3.0
|
| 56 |
+
export DA3_PERC_GAIN_MAX=8.0
|
| 57 |
+
|
| 58 |
+
export DA3_SCALE=1.0
|
| 59 |
+
export DA3_INIT_STD=0.01
|
| 60 |
+
export DA3_LR_GROUPS=1
|
| 61 |
+
|
| 62 |
+
export DA3_MODEL_NAME=depth-anything/DA3-Large-1.1
|
| 63 |
+
export DA3_OUT_LAYERS=11,15,19,23
|
| 64 |
+
export B1K_USE_GT_DEPTH=1
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
`DA3_DEPTH_DROPOUT=0.5` must match the stored configuration but is inactive during inference because
|
| 68 |
+
the bank builder receives no training dropout RNG.
|
| 69 |
+
|
| 70 |
+
Load strictly:
|
| 71 |
+
|
| 72 |
+
```python
|
| 73 |
+
params = restore_params(checkpoint_dir / "params", dtype=jnp.bfloat16)
|
| 74 |
+
model = train_config.model.load(params, remove_extra_params=False)
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
Do not allow extra parameters to be silently removed. A stock configuration with `da3=None` can
|
| 78 |
+
otherwise discard the entire spatial subtree and appear to load successfully.
|
| 79 |
+
|
| 80 |
+
## Required per-timestep inputs
|
| 81 |
+
|
| 82 |
+
View order is fixed:
|
| 83 |
+
|
| 84 |
+
```text
|
| 85 |
+
0 = zed/head
|
| 86 |
+
1 = left wrist RealSense
|
| 87 |
+
2 = right wrist RealSense
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
For every policy call, construct:
|
| 91 |
+
|
| 92 |
+
| Observation field | Shape | Meaning |
|
| 93 |
+
|---|---:|---|
|
| 94 |
+
| `da3_features` | `[B,4,3,1024,18,18]` | DA3-Large taps, bf16 bits stored as `uint16` |
|
| 95 |
+
| `da3_ray` | `[B,3,3,18,18]` | DA3 camera-frame unit rays |
|
| 96 |
+
| `da3_depth` | `[B,3,1,18,18]` | **GT optical-axis Z-depth in metres** |
|
| 97 |
+
| `camera_extrinsics` | `[B,3,4,4]` | robot-to-camera transform in OpenCV convention |
|
| 98 |
+
| `lang_feat` | `[B,32,1024]` | ModernBERT task feature |
|
| 99 |
+
| `lang_mask` | `[B,32]` | corresponding valid-token mask |
|
| 100 |
+
|
| 101 |
+
The normal PiBehavior image, image-mask, state, task, and subtask fields are still required.
|
| 102 |
+
|
| 103 |
+
## Spatial input procedure
|
| 104 |
+
|
| 105 |
+
Use `reference/large_gt_spatial_inputs.py::build_spatial_inputs`:
|
| 106 |
+
|
| 107 |
+
1. Arrange the three RGB images in head/left/right order.
|
| 108 |
+
2. Supply matching robot-to-camera OpenCV extrinsics.
|
| 109 |
+
3. Rescale camera intrinsics to the 252x252 DA3 raster.
|
| 110 |
+
4. Run the frozen DA3-Large extractor to obtain features and rays.
|
| 111 |
+
5. Ignore the extractor's third output—DA3-Large depth is non-metric.
|
| 112 |
+
6. Convert the matching GT linear depth images to metres.
|
| 113 |
+
7. Area-resize each native depth map directly to 18x18.
|
| 114 |
+
8. Put this tensor into `Observation.da3_depth`.
|
| 115 |
+
|
| 116 |
+
The 2D patch coordinates are generated internally by `SpatialBankBuilder` on the same 18x18 grid.
|
| 117 |
+
The address/key path is:
|
| 118 |
+
|
| 119 |
+
```text
|
| 120 |
+
2D patch position + Plücker ray + view identity
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
The payload/value path is:
|
| 124 |
+
|
| 125 |
+
```text
|
| 126 |
+
DA3-Large latent feature + encoded log GT depth
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
Cross-view fusion then uses the supplied camera extrinsics. Depth does not independently alter the
|
| 130 |
+
ray or 2D position; correctness comes from all tensors referring to the same camera, timestamp, and
|
| 131 |
+
patch cell.
|
| 132 |
+
|
| 133 |
+
## Depth conventions—critical
|
| 134 |
+
|
| 135 |
+
Training used BEHAVIOR-1K `observation.depth_linear.*` videos:
|
| 136 |
+
|
| 137 |
+
- native `gray12le` values are millimetres stored in `uint16`;
|
| 138 |
+
- convert with `native.astype(float32) / 1000.0`;
|
| 139 |
+
- do **not** request `gray16le`, which left-shifts the 12-bit samples by four bits;
|
| 140 |
+
- depth is optical-axis Z-depth, not Euclidean range along the ray;
|
| 141 |
+
- invalid/nonpositive depth should be handled consistently with the simulator before log-depth;
|
| 142 |
+
- area interpolation was used from the native square raster to 18x18.
|
| 143 |
+
|
| 144 |
+
If the evaluation environment emits metres already, do not divide again.
|
| 145 |
+
|
| 146 |
+
## Camera convention
|
| 147 |
+
|
| 148 |
+
Training camera pose metadata stored the camera pose in the robot frame. It was converted to
|
| 149 |
+
robot-to-camera and then from OmniGibson OpenGL optical axes to OpenCV:
|
| 150 |
+
|
| 151 |
+
```python
|
| 152 |
+
GL_TO_CV = diag(1, -1, -1, 1)
|
| 153 |
+
robot_to_camera_cv = GL_TO_CV @ inverse(camera_pose_in_robot)
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
If the evaluation system already supplies robot-to-camera OpenCV extrinsics, use them directly.
|
| 157 |
+
Applying this conversion twice is incorrect.
|
| 158 |
+
|
| 159 |
+
Training intrinsics used the OmniGibson camera ratio:
|
| 160 |
+
|
| 161 |
+
```text
|
| 162 |
+
fx = fy = image_width * 17.0 / 20.995
|
| 163 |
+
cx = cy = image_width / 2
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
Real intrinsics supplied by the simulator are preferable. They must describe the image after
|
| 167 |
+
rescaling to 252x252.
|
| 168 |
+
|
| 169 |
+
## Language/task conditioning
|
| 170 |
+
|
| 171 |
+
Use the `preparing_lunch_box` entry from `reference/modernbert_b1k_tasks.pkl`. The checkpoint uses
|
| 172 |
+
task embedding index 12. Do not substitute a different task index even if the 2026 dataset has been
|
| 173 |
+
reordered.
|
| 174 |
+
|
| 175 |
+
## Serving integration
|
| 176 |
+
|
| 177 |
+
The existing `scripts/serve_b1k.py` and `b1k.policies.policy_config` do not automatically run the
|
| 178 |
+
frozen DA3 extractor or attach GT depth. The evaluation system must insert the spatial-input
|
| 179 |
+
procedure before `PiBehaviorPolicy.infer`/`model.sample_actions`.
|
| 180 |
+
|
| 181 |
+
At batch-size-1 serving:
|
| 182 |
+
|
| 183 |
+
- keep `DA3_BANK_CENTER=0`;
|
| 184 |
+
- do not introduce a population-mean centering workaround;
|
| 185 |
+
- do not switch centering on;
|
| 186 |
+
- ensure all six required spatial fields are present and finite.
|
| 187 |
+
|
| 188 |
+
## Sanity gates before simulator evaluation
|
| 189 |
+
|
| 190 |
+
For one batch-size-1 observation:
|
| 191 |
+
|
| 192 |
+
```text
|
| 193 |
+
features shape = (1,4,3,1024,18,18), dtype uint16
|
| 194 |
+
ray shape = (1,3,3,18,18), norm approximately 1
|
| 195 |
+
depth shape = (1,3,1,18,18), finite positive metres
|
| 196 |
+
extrinsics = (1,3,4,4), finite rigid transforms
|
| 197 |
+
banks = main (1,128,1024), left/right (1,96,1024)
|
| 198 |
+
```
|
| 199 |
+
|
| 200 |
+
Banks must be nonzero and change when the observation changes. Load failure, missing spatial fields,
|
| 201 |
+
wrong feature dtype, view-order mismatch, millimetres passed as metres, or `DA3_BANK_CENTER=1` can
|
| 202 |
+
invalidate evaluation without an obvious server crash.
|
| 203 |
+
|
| 204 |
+
## Training recipe and result
|
| 205 |
+
|
| 206 |
+
- Dataset: task 12, 200 episodes, 1,643,060 valid action-horizon frame samples.
|
| 207 |
+
- Initialization: 50-task PiBehavior meta checkpoint.
|
| 208 |
+
- Steps: 20,000.
|
| 209 |
+
- Batch: 126.
|
| 210 |
+
- Topology: one DA3-Large extractor GPU plus three JAX training GPUs.
|
| 211 |
+
- Spatial depth dropout: 0.5.
|
| 212 |
+
- Live bank centering: disabled.
|
| 213 |
+
- Final checkpoint: step 19999. Final logged metric at step 19950: action loss `0.0621`, total loss
|
| 214 |
+
`0.0972`, gradient norm `0.4168`. The adjacent-batch action loss at step 19900 was `0.0611`;
|
| 215 |
+
normal batch-to-batch noise remains.
|
| 216 |
+
- A causal correct/shuffled/mean/zeroed spatial-bank diagnostic is run separately after training;
|
| 217 |
+
its result is not required to load the checkpoint and should not be inferred from training loss.
|
legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/metadata.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"vocab_size": 1024,
|
| 3 |
+
"scale": 10.0,
|
| 4 |
+
"encoded_dims": "0:6,7:23",
|
| 5 |
+
"encoded_dim_ranges": [
|
| 6 |
+
[
|
| 7 |
+
0,
|
| 8 |
+
6
|
| 9 |
+
],
|
| 10 |
+
[
|
| 11 |
+
7,
|
| 12 |
+
23
|
| 13 |
+
]
|
| 14 |
+
],
|
| 15 |
+
"total_encoded_dims": 22,
|
| 16 |
+
"action_horizon": 30,
|
| 17 |
+
"num_training_chunks": 5935465,
|
| 18 |
+
"compression_stats": {
|
| 19 |
+
"compression_ratio": 3.644254501482549,
|
| 20 |
+
"mean_token_length": 181.107,
|
| 21 |
+
"p99_token_length": 658.0,
|
| 22 |
+
"min_token_length": 35.0,
|
| 23 |
+
"max_token_length": 660.0
|
| 24 |
+
}
|
| 25 |
+
}
|
legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/processing_action_tokenizer.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from typing import ClassVar
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
from scipy.fft import dct
|
| 6 |
+
from scipy.fft import idct
|
| 7 |
+
from tokenizers import ByteLevelBPETokenizer
|
| 8 |
+
from tokenizers.trainers import BpeTrainer
|
| 9 |
+
from transformers import PreTrainedTokenizerFast
|
| 10 |
+
from transformers.processing_utils import ProcessorMixin
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class UniversalActionProcessor(ProcessorMixin):
|
| 14 |
+
attributes: ClassVar[list[str]] = ["bpe_tokenizer"]
|
| 15 |
+
bpe_tokenizer_class: str = "AutoTokenizer"
|
| 16 |
+
|
| 17 |
+
def __init__(
|
| 18 |
+
self,
|
| 19 |
+
bpe_tokenizer: PreTrainedTokenizerFast,
|
| 20 |
+
scale: float = 10,
|
| 21 |
+
vocab_size: int = 1024,
|
| 22 |
+
min_token: int = 0,
|
| 23 |
+
*,
|
| 24 |
+
action_dim: int | None = None,
|
| 25 |
+
time_horizon: int | None = None,
|
| 26 |
+
):
|
| 27 |
+
self.scale = scale
|
| 28 |
+
self.vocab_size = vocab_size
|
| 29 |
+
self.min_token = min_token
|
| 30 |
+
|
| 31 |
+
# Action horizon and dimension needed during decoding. These can be specified
|
| 32 |
+
# in three ways (in order of priority):
|
| 33 |
+
# 1. passed in as kwargs to decode()
|
| 34 |
+
# 2. in the constructor
|
| 35 |
+
# 3. cached from the last time decode() was called
|
| 36 |
+
self.time_horizon = time_horizon
|
| 37 |
+
self.action_dim = action_dim
|
| 38 |
+
self.called_time_horizon = time_horizon
|
| 39 |
+
self.called_action_dim = action_dim
|
| 40 |
+
|
| 41 |
+
super().__init__(bpe_tokenizer)
|
| 42 |
+
|
| 43 |
+
def __call__(self, action_chunk: np.array) -> np.array:
|
| 44 |
+
assert action_chunk.ndim <= 3, "Only 3 dimensions supported: [batch, timesteps, action_dim]"
|
| 45 |
+
if action_chunk.ndim == 2:
|
| 46 |
+
action_chunk = action_chunk[None, ...]
|
| 47 |
+
|
| 48 |
+
# Cache the time horizon and action dimension for decoding
|
| 49 |
+
self.called_time_horizon = action_chunk.shape[-2]
|
| 50 |
+
self.called_action_dim = action_chunk.shape[-1]
|
| 51 |
+
|
| 52 |
+
dct_coeff = dct(action_chunk, axis=1, norm="ortho")
|
| 53 |
+
dct_coeff = np.around(dct_coeff * self.scale)
|
| 54 |
+
tokens = []
|
| 55 |
+
for elem in dct_coeff:
|
| 56 |
+
token_str = "".join(map(chr, np.maximum(elem.flatten() - self.min_token, 0).astype(int)))
|
| 57 |
+
tokens.append(self.bpe_tokenizer(token_str)["input_ids"])
|
| 58 |
+
return tokens
|
| 59 |
+
|
| 60 |
+
def decode(
|
| 61 |
+
self,
|
| 62 |
+
tokens: list[list[int]],
|
| 63 |
+
*,
|
| 64 |
+
time_horizon: int | None = None,
|
| 65 |
+
action_dim: int | None = None,
|
| 66 |
+
) -> np.array:
|
| 67 |
+
self.time_horizon = time_horizon or self.time_horizon or self.called_time_horizon
|
| 68 |
+
self.action_dim = action_dim or self.action_dim or self.called_action_dim
|
| 69 |
+
|
| 70 |
+
# Cache the time horizon and action dimension for the next call
|
| 71 |
+
self.called_time_horizon = self.time_horizon
|
| 72 |
+
self.called_action_dim = self.action_dim
|
| 73 |
+
|
| 74 |
+
assert (
|
| 75 |
+
self.time_horizon is not None and self.action_dim is not None
|
| 76 |
+
), "Tokenizer not initialized, call encode() once or pass in time_horizon and action_dim."
|
| 77 |
+
|
| 78 |
+
decoded_actions = []
|
| 79 |
+
for token in tokens:
|
| 80 |
+
try:
|
| 81 |
+
decoded_tokens = self.bpe_tokenizer.decode(token)
|
| 82 |
+
decoded_dct_coeff = np.array(list(map(ord, decoded_tokens))) + self.min_token
|
| 83 |
+
decoded_dct_coeff = decoded_dct_coeff.reshape(-1, self.action_dim)
|
| 84 |
+
assert (
|
| 85 |
+
decoded_dct_coeff.shape
|
| 86 |
+
== (
|
| 87 |
+
self.time_horizon,
|
| 88 |
+
self.action_dim,
|
| 89 |
+
)
|
| 90 |
+
), f"Decoded DCT coefficients have shape {decoded_dct_coeff.shape}, expected ({self.time_horizon}, {self.action_dim})"
|
| 91 |
+
except Exception as e:
|
| 92 |
+
print(f"Error decoding tokens: {e}")
|
| 93 |
+
print(f"Tokens: {token}")
|
| 94 |
+
decoded_dct_coeff = np.zeros((self.time_horizon, self.action_dim))
|
| 95 |
+
decoded_actions.append(idct(decoded_dct_coeff / self.scale, axis=0, norm="ortho"))
|
| 96 |
+
return np.stack(decoded_actions)
|
| 97 |
+
|
| 98 |
+
@classmethod
|
| 99 |
+
def fit(
|
| 100 |
+
cls,
|
| 101 |
+
action_data: list[np.array],
|
| 102 |
+
scale: float = 10,
|
| 103 |
+
vocab_size: int = 1024,
|
| 104 |
+
*,
|
| 105 |
+
time_horizon: int | None = None,
|
| 106 |
+
action_dim: int | None = None,
|
| 107 |
+
) -> "UniversalActionProcessor":
|
| 108 |
+
# Run DCT over all inputs
|
| 109 |
+
dct_tokens = [dct(a, axis=0, norm="ortho").flatten() for a in action_data]
|
| 110 |
+
|
| 111 |
+
# Quantize and find min token
|
| 112 |
+
max_token = int(np.around(np.concatenate(dct_tokens) * scale).max())
|
| 113 |
+
min_token = int(np.around(np.concatenate(dct_tokens) * scale).min())
|
| 114 |
+
min_vocab_size = max_token - min_token
|
| 115 |
+
|
| 116 |
+
assert (
|
| 117 |
+
min_vocab_size <= vocab_size
|
| 118 |
+
), f"Vocab size {vocab_size} is too small for the range of tokens {min_vocab_size}"
|
| 119 |
+
if min_vocab_size + 100 > vocab_size:
|
| 120 |
+
logging.warning(
|
| 121 |
+
f"Initial alphabet size {min_vocab_size} is almost as large as the vocab"
|
| 122 |
+
f"size {vocab_size}, consider increasing vocab size"
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
# Make token iterator for BPE training
|
| 126 |
+
def _token_iter():
|
| 127 |
+
for tokens in dct_tokens:
|
| 128 |
+
rounded_tokens = np.around(tokens * scale) - min_token
|
| 129 |
+
rounded_tokens = rounded_tokens.astype(int)
|
| 130 |
+
string = "".join(map(chr, rounded_tokens))
|
| 131 |
+
yield string
|
| 132 |
+
|
| 133 |
+
# Train BPE tokenizer
|
| 134 |
+
bpe = ByteLevelBPETokenizer()
|
| 135 |
+
|
| 136 |
+
# Set up the entire range of possible tokens as the initial alphabet
|
| 137 |
+
alphabet = [chr(i) for i in range(max_token - min_token + 1)]
|
| 138 |
+
trainer = BpeTrainer(
|
| 139 |
+
vocab_size=vocab_size,
|
| 140 |
+
min_frequency=2,
|
| 141 |
+
show_progress=True,
|
| 142 |
+
special_tokens=[],
|
| 143 |
+
initial_alphabet=alphabet,
|
| 144 |
+
max_token_length=10000,
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
# Train the inner tokenizer (don't use ByteLevelBPETokenizer.train_from_iterator()
|
| 148 |
+
# because it doesn't support custom alphabets)
|
| 149 |
+
bpe._tokenizer.train_from_iterator(_token_iter(), trainer=trainer)
|
| 150 |
+
|
| 151 |
+
return cls(
|
| 152 |
+
PreTrainedTokenizerFast(tokenizer_object=bpe, clean_up_tokenization_spaces=False),
|
| 153 |
+
scale=scale,
|
| 154 |
+
vocab_size=vocab_size,
|
| 155 |
+
min_token=min_token,
|
| 156 |
+
time_horizon=time_horizon,
|
| 157 |
+
action_dim=action_dim,
|
| 158 |
+
)
|
legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/processor_config.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"action_dim": 22,
|
| 3 |
+
"auto_map": {
|
| 4 |
+
"AutoProcessor": "processing_action_tokenizer.UniversalActionProcessor"
|
| 5 |
+
},
|
| 6 |
+
"min_token": -55,
|
| 7 |
+
"processor_class": "UniversalActionProcessor",
|
| 8 |
+
"scale": 10.0,
|
| 9 |
+
"time_horizon": 30,
|
| 10 |
+
"vocab_size": 1024
|
| 11 |
+
}
|
legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/special_tokens_map.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{}
|
legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/tokenizer_config.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"added_tokens_decoder": {},
|
| 3 |
+
"auto_map": {
|
| 4 |
+
"AutoProcessor": "processing_action_tokenizer.UniversalActionProcessor"
|
| 5 |
+
},
|
| 6 |
+
"clean_up_tokenization_spaces": false,
|
| 7 |
+
"extra_special_tokens": {},
|
| 8 |
+
"model_max_length": 1000000000000000019884624838656,
|
| 9 |
+
"processor_class": "UniversalActionProcessor",
|
| 10 |
+
"tokenizer_class": "PreTrainedTokenizerFast"
|
| 11 |
+
}
|
legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/norm_stats.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ccd14a0210fc59b2d2726ba599cc0c4b81347395dd60d2a15b334b28ed15a80b
|
| 3 |
+
size 18009212
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/launch/launch_b1k_newbank.sh
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# NEW-BANK run (2026-07-24): K/V-split + moved bank_token_embeds + PERCEIVER LOCALITY + CROSS-VIEW 3D
|
| 3 |
+
# FUSION, all spatial addons training from step 0. Fresh spatial (new arch), base warm-started from META.
|
| 4 |
+
# Targets the confirmed weakness: geometry was load-bearing but NOT robustly per-sample specific
|
| 5 |
+
# (multi-batch shuffle-damage ~0). Cross-view fusion (integrated 3D) + locality (per-location detail)
|
| 6 |
+
# aim to make it discriminative. Judge by MULTI-BATCH shuffle-damage separating from zero.
|
| 7 |
+
#
|
| 8 |
+
# LR (user-specified 2026-07-24, plateau schedules):
|
| 9 |
+
# spatial (core & geom TOGETHER): 1e-4 from step 0, FLAT to 10k, cosine to 1e-5 (1/10) at 20k. No ramp.
|
| 10 |
+
# vlm (base+action expert): frozen 0-1k; 1e-6 @1k ramp to 1e-5 by 5k; flat to 10k; cosine to 1e-6 @20k.
|
| 11 |
+
# 4 GPUs (HOLDOUT=2 => 2 train + 2 extract), BS=96 (falls back if OOM), 20k steps.
|
| 12 |
+
set -uo pipefail
|
| 13 |
+
GPUS="${1:-4,5,6,7}"; ACT="${2:-clean_up_your_desk}"
|
| 14 |
+
export EXP_OVERRIDE=${EXP_OVERRIDE:-b1k_da3_newbank_desk}
|
| 15 |
+
export B1K_INIT_PARAMS=${B1K_INIT_PARAMS:-/work/jack/behavior1k/checkpoints/behavior_50t_checkpoint/params}
|
| 16 |
+
|
| 17 |
+
# --- deployable bank architecture ---
|
| 18 |
+
# Keep the structural K/V split, but do NOT subtract the live batch mean. Live batch centering makes
|
| 19 |
+
# every bank exactly zero when policy serving uses batch size 1. Existing centered checkpoints need a
|
| 20 |
+
# frozen population mean on the evaluation side; new deployable training runs keep centering off.
|
| 21 |
+
export DA3_KV_SPLIT=1 DA3_BANK_CENTER=0 DA3_SCALE=1.0 DA3_INIT_STD=0.01
|
| 22 |
+
export DA3_PERC_LOCALITY=1 DA3_CROSS_VIEW=1 DA3_CROSS_VIEW_DEPTH=2
|
| 23 |
+
export DA3_DEPTH_DROPOUT=0.5
|
| 24 |
+
export DA3_AUX_GEOM_HEAD=0 DA3_DEPTH_TARGET_ONLY=0
|
| 25 |
+
|
| 26 |
+
# --- spatial (core & geom together): flat 1e-4 from step 0, decay@10k to 1/10 ---
|
| 27 |
+
# override the base launcher's geom delay/ramp defaults (500/2500) so geom == core, from step 0.
|
| 28 |
+
# Phase *timings* (DECAY_START) are overridable so a shortened run can scale each phase by the same
|
| 29 |
+
# relative amount (e.g. 20k run -> DECAY_START=5000); LR *magnitudes* (PEAK/END) stay fixed.
|
| 30 |
+
export LR_CORE_PEAK=1.0e-4 LR_CORE_END=1.0e-5 LR_CORE_DECAY_START=${LR_CORE_DECAY_START:-10000} LR_CORE_DELAY=0 LR_CORE_RAMP=0
|
| 31 |
+
export LR_GEOM_PEAK=1.0e-4 LR_GEOM_END=1.0e-5 LR_GEOM_DECAY_START=${LR_GEOM_DECAY_START:-10000} LR_GEOM_DELAY=0 LR_GEOM_RAMP=0
|
| 32 |
+
# --- vlm: frozen 0-1k, ramp 1e-6->1e-5 by 5k, flat, decay@10k to 1/10 ---
|
| 33 |
+
export LR_VLM_DELAY=${LR_VLM_DELAY:-1000} LR_VLM_RAMP=${LR_VLM_RAMP:-4000} LR_VLM_RAMP_START=1.0e-6
|
| 34 |
+
export LR_VLM_PEAK=1.0e-5 LR_VLM_END=1.0e-6 LR_VLM_DECAY_START=${LR_VLM_DECAY_START:-10000}
|
| 35 |
+
export WARMUP=1 # unused (all groups use plateau schedules) but keep >0
|
| 36 |
+
|
| 37 |
+
# --- tuned qk-norm regime ---
|
| 38 |
+
export DA3_QK_NORM=1 DA3_PERC_NORM_FINAL=1 DA3_PERC_NORM_OUT=0 DA3_POS_EMB_SCALE=0.25
|
| 39 |
+
export DA3_LOGIT_GAIN_INIT=3.0 DA3_INJ_GAIN_MAX=8.0 DA3_PERC_GAIN_INIT=3.0 DA3_PERC_GAIN_MAX=8.0
|
| 40 |
+
export DA3_SPATIAL_EPS=1e-16 DA3_SPATIAL_WD=1e-4
|
| 41 |
+
|
| 42 |
+
export BS=${BS:-96} STEPS=${STEPS:-20000} LR_DECAY=${LR_DECAY:-20000} HOLDOUT=${HOLDOUT:-2}
|
| 43 |
+
exec bash /work/jack/launch_b1k_v2.sh "$GPUS" "$ACT"
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/launch/launch_b1k_task12_large_gtdepth.sh
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Task-12 experiment: DA3-Large-1.1 features + dataset GT metric depth.
|
| 3 |
+
# Default is the exactly matched 2 extractor + 2 JAX training, BS=128 topology.
|
| 4 |
+
# Optional throughput topology: HOLDOUT=1 BS=126 (1 extractor + 3 training), but do not use that for
|
| 5 |
+
# the strict controlled comparison because it changes batch size and optimizer sample exposure.
|
| 6 |
+
set -uo pipefail
|
| 7 |
+
export B1K_2026_ROOT=/work/jack/behavior1k/data/behavior_2026_tasks0_49
|
| 8 |
+
export EXP_OVERRIDE=${EXP_OVERRIDE:-b1k_task12_large_gtdepth_newbank}
|
| 9 |
+
export DA3_MODEL_NAME=depth-anything/DA3-Large-1.1
|
| 10 |
+
export DA3_OUT_LAYERS=11,15,19,23
|
| 11 |
+
export DA3_CHANNELS=1024
|
| 12 |
+
export B1K_USE_GT_DEPTH=1
|
| 13 |
+
export BS=${BS:-128} STEPS=${STEPS:-20000} LR_DECAY=${LR_DECAY:-20000} HOLDOUT=${HOLDOUT:-2}
|
| 14 |
+
export LR_CORE_DECAY_START=${LR_CORE_DECAY_START:-5000}
|
| 15 |
+
export LR_GEOM_DECAY_START=${LR_GEOM_DECAY_START:-5000}
|
| 16 |
+
export LR_VLM_DELAY=${LR_VLM_DELAY:-500} LR_VLM_RAMP=${LR_VLM_RAMP:-2000}
|
| 17 |
+
export LR_VLM_DECAY_START=${LR_VLM_DECAY_START:-5000}
|
| 18 |
+
exec bash /work/jack/launch_b1k_newbank.sh "${1:-0,1,2,3}" preparing_lunch_box
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/launch/launch_b1k_v2.sh
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Task-#2+ launcher: all pipeline optimizations + hyperparameters tuned from the task-#1 run.
|
| 3 |
+
# Arg1 = physical GPU list. 8 GPUs => 4 train + 4 dedicated extraction; 4 GPUs => inline (2 jobs in parallel).
|
| 4 |
+
# Arg2 = activity name (default clean_up_your_desk). EXP name is derived from it.
|
| 5 |
+
#
|
| 6 |
+
# DELIBERATELY SEPARATE from launch_b1k_robopro.sh so the in-flight task-#1 run (and its babysitter,
|
| 7 |
+
# which would relaunch on a crash) keeps its original recipe untouched.
|
| 8 |
+
set -uo pipefail
|
| 9 |
+
GPUS="${1:-0,1,2,3,4,5,6,7}"
|
| 10 |
+
ACT="${2:-clean_up_your_desk}"
|
| 11 |
+
cd /work/jack/behavior-1k-solution
|
| 12 |
+
NGPU=$(echo "$GPUS" | tr ',' '\n' | grep -c .)
|
| 13 |
+
|
| 14 |
+
# ---- hyperparameters (tuned from task #1) --------------------------------------------------------
|
| 15 |
+
# Task #1 evidence: action_loss 0.0678 @34k x BS128 (= 4.35M samples) already matched the no-DA3
|
| 16 |
+
# baseline's converged 0.069; 0.0654 @38k (4.86M). Target ~4.6M samples with the LR schedule MATCHED
|
| 17 |
+
# to the budget (task #1 decayed over 50k, so its LR was still high mid-run -- a matched schedule
|
| 18 |
+
# reaches comparable loss in far fewer steps).
|
| 19 |
+
# BS default is derived AFTER the topology is known (see below) so it is always divisible by the
|
| 20 |
+
# training-GPU count. A hardcoded 196 crashed an 8-GPU relaunch with
|
| 21 |
+
# "Batch size 196 must be divisible by the number of devices 8" -- which would have tripped the
|
| 22 |
+
# babysitter circuit breaker on any crash recovery.
|
| 23 |
+
BS_EXPLICIT="${BS:-}"
|
| 24 |
+
STEPS=${STEPS:-25000} # 25000 x 196 = 4.90M samples (task#1 ref: 0.0654 action @4.86M)
|
| 25 |
+
LR_DECAY=${LR_DECAY:-$STEPS} # schedule matched to budget
|
| 26 |
+
WARMUP=${WARMUP:-500} # vlm/core ramp 0 -> peak by step 500
|
| 27 |
+
LR_VLM_PEAK=${LR_VLM_PEAK:-4.0e-5}; LR_VLM_END=${LR_VLM_END:-2.0e-6}
|
| 28 |
+
LR_CORE_PEAK=${LR_CORE_PEAK:-7.0e-4}; LR_CORE_END=${LR_CORE_END:-5.0e-5}
|
| 29 |
+
LR_GEOM_PEAK=${LR_GEOM_PEAK:-7.0e-4}; LR_GEOM_END=${LR_GEOM_END:-1.0e-4}
|
| 30 |
+
# Geometry group gets a DELAYED, SLOW phase-in: held at exactly 0 for the first 500 steps (while the
|
| 31 |
+
# pretrained trunk does its normal warmup), then ramped 0 -> peak over 2.5k steps before cosine decay.
|
| 32 |
+
# Geometry modules are sensitive to being driven hard from random init.
|
| 33 |
+
LR_GEOM_DELAY=${LR_GEOM_DELAY:-500}
|
| 34 |
+
LR_GEOM_RAMP=${LR_GEOM_RAMP:-2500}
|
| 35 |
+
|
| 36 |
+
# ---- topology (unified: works at ANY GPU count, split-vs-inline decided by HOLDOUT) ---------------
|
| 37 |
+
# HOLDOUT=0: all visible GPUs both train and extract inline (extraction competes with training).
|
| 38 |
+
# HOLDOUT=k>0: hold the first k GPUs OUT of the training mesh for DEDICATED extraction; train on the
|
| 39 |
+
# remaining NGPU-k. Extraction runs continuously (never pauses for the step) and fills the prefetch
|
| 40 |
+
# queue, so training on the rest is fed steadily instead of bursting. Measured best on 8 GPUs at 4+4
|
| 41 |
+
# (1:1 extract:train). The same 1:1 ratio halves cleanly to 4 GPUs as 2+2.
|
| 42 |
+
HOLDOUT=${HOLDOUT:-0}; QDEPTH=${QDEPTH:-6}; FWD=${FWD:-32}
|
| 43 |
+
if [ "$NGPU" -ge 8 ]; then NW=${NW:-32}; else NW=${NW:-16}; fi
|
| 44 |
+
if [ "$HOLDOUT" -gt 0 ]; then
|
| 45 |
+
EXTRACT_DEVS=$(seq -s, -f 'cuda:%g' 0 $((HOLDOUT-1)))
|
| 46 |
+
GMODE="${NGPU}-GPU: $((NGPU-HOLDOUT)) train + $HOLDOUT dedicated extract"
|
| 47 |
+
else
|
| 48 |
+
EXTRACT_DEVS=$(seq -s, -f 'cuda:%g' 0 $((NGPU-1)))
|
| 49 |
+
GMODE="${NGPU}-GPU: all train, inline extraction"
|
| 50 |
+
fi
|
| 51 |
+
# NUMA pin: GPUs 0-3 sit on node 0 (CPUs 0-59,120-179), GPUs 4-7 on node 1 (60-119,180-239).
|
| 52 |
+
# Pinning the process (dataloader workers inherit the affinity) keeps memory local via Linux
|
| 53 |
+
# first-touch, avoiding cross-socket traffic on every batch. Skipped if the run spans both nodes.
|
| 54 |
+
_g_first=$(echo "$GPUS" | cut -d, -f1); _g_last=$(echo "$GPUS" | tr ',' '\n' | tail -1)
|
| 55 |
+
if [ "$_g_last" -le 3 ] 2>/dev/null; then PIN="taskset -c 0-59,120-179"; PINNOTE="node0"
|
| 56 |
+
elif [ "$_g_first" -ge 4 ] 2>/dev/null; then PIN="taskset -c 60-119,180-239"; PINNOTE="node1"
|
| 57 |
+
else PIN=""; PINNOTE="none (spans both sockets)"; fi
|
| 58 |
+
|
| 59 |
+
TRAIN_GPUS=$((NGPU - HOLDOUT))
|
| 60 |
+
# 32 samples/GPU is the measured throughput peak (16->21.3, 32->27.8, 64->25.1 samp/s).
|
| 61 |
+
BS=${BS_EXPLICIT:-$((32 * TRAIN_GPUS))}
|
| 62 |
+
[ $((BS % TRAIN_GPUS)) -eq 0 ] || { echo "ERROR: BS=$BS not divisible by $TRAIN_GPUS training GPUs"; exit 1; }
|
| 63 |
+
|
| 64 |
+
EXP="${EXP_OVERRIDE:-b1k_da3_v2_${ACT}}"
|
| 65 |
+
CKDIR=outputs/checkpoints/pi_behavior_b1k_fast/$EXP
|
| 66 |
+
if ls "$CKDIR" 2>/dev/null | grep -qE '^[0-9]+$'; then RES=1; OVW=0; MODE="RESUME from $(ls "$CKDIR"|grep -E '^[0-9]+$'|sort -n|tail -1)"; else RES=0; OVW=1; MODE="FRESH"; fi
|
| 67 |
+
LOG=/work/jack/${EXP}_train.log
|
| 68 |
+
echo "[$(date +%H:%M)] launching $EXP ($MODE, $GMODE) BS=$BS STEPS=$STEPS on GPUs $GPUS" | tee -a /work/jack/b1k_v2_babysitter.log
|
| 69 |
+
|
| 70 |
+
CUDA_VISIBLE_DEVICES="$GPUS" \
|
| 71 |
+
XLA_PYTHON_CLIENT_MEM_FRACTION=0.80 XLA_PYTHON_CLIENT_ALLOCATOR=platform \
|
| 72 |
+
XLA_FLAGS="--xla_gpu_enable_latency_hiding_scheduler=true --xla_gpu_all_reduce_combine_threshold_bytes=8388608 --xla_gpu_enable_highest_priority_async_stream=true" \
|
| 73 |
+
JAX_COMPILATION_CACHE_DIR=/work/jack/.jax_compile_cache \
|
| 74 |
+
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \
|
| 75 |
+
PYTHONUNBUFFERED=1 \
|
| 76 |
+
B1K_HOLDOUT_GPU0=$HOLDOUT \
|
| 77 |
+
B1K_EXTRACT_DEVICES=$EXTRACT_DEVS B1K_DECODE_THREADS=1 \
|
| 78 |
+
B1K_SHARED_DECODE=1 B1K_DLPACK=1 B1K_TORCH_COLLATE=1 \
|
| 79 |
+
B1K_REMAT=${B1K_REMAT:-full} \
|
| 80 |
+
B1K_DA3_FWD_CHUNK=${B1K_DA3_FWD_CHUNK:-$FWD} \
|
| 81 |
+
B1K_EXTRACT_QUEUE=$QDEPTH \
|
| 82 |
+
RAYON_NUM_THREADS=1 OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 MKL_NUM_THREADS=1 \
|
| 83 |
+
NUMEXPR_NUM_THREADS=1 POLARS_MAX_THREADS=1 TOKENIZERS_PARALLELISM=false \
|
| 84 |
+
HF_HOME=/work/jack/da3xvla_workspace/hf_cache HF_HUB_OFFLINE=1 \
|
| 85 |
+
B1K_2026_ROOT=${B1K_2026_ROOT:-/work/jack/behavior1k/data/behavior_2026_task29_42} \
|
| 86 |
+
USE_DA3_FULL=1 DA3_INIT_STD=${DA3_INIT_STD:-0.01} DA3_LOGIT_GAIN=1 DA3_SCALE=${DA3_SCALE:-2.0} DA3_LR_GROUPS=1 \
|
| 87 |
+
DA3_BANK_CENTER=${DA3_BANK_CENTER:-0} \
|
| 88 |
+
DA3_AUX_GEOM_HEAD=${DA3_AUX_GEOM_HEAD:-0} DA3_AUX_GEOM_WEIGHT=${DA3_AUX_GEOM_WEIGHT:-0.0} \
|
| 89 |
+
DA3_DEPTH_TARGET_ONLY=${DA3_DEPTH_TARGET_ONLY:-0} \
|
| 90 |
+
DA3_KV_SPLIT=${DA3_KV_SPLIT:-0} DA3_DEPTH_DROPOUT=${DA3_DEPTH_DROPOUT:-0.0} \
|
| 91 |
+
DA3_PERC_LOCALITY=${DA3_PERC_LOCALITY:-0} DA3_CROSS_VIEW=${DA3_CROSS_VIEW:-0} DA3_CROSS_VIEW_DEPTH=${DA3_CROSS_VIEW_DEPTH:-2} \
|
| 92 |
+
LR_VLM_RAMP_START=${LR_VLM_RAMP_START:-0.0} \
|
| 93 |
+
DA3_SPATIAL_EPS=${DA3_SPATIAL_EPS:-1e-16} \
|
| 94 |
+
DA3_QK_NORM=${DA3_QK_NORM:-1} DA3_PERC_NORM_FINAL=${DA3_PERC_NORM_FINAL:-1} DA3_POS_EMB_SCALE=${DA3_POS_EMB_SCALE:-0.25} \
|
| 95 |
+
DA3_SPATIAL_WD=${DA3_SPATIAL_WD:-1e-4} \
|
| 96 |
+
DA3_PERC_LOGIT_GAIN=1 DA3_PERC_NORM_OUT=${DA3_PERC_NORM_OUT:-1} \
|
| 97 |
+
DA3_LOGIT_GAIN_INIT=${DA3_LOGIT_GAIN_INIT:-3.0} DA3_INJ_GAIN_MAX=${DA3_INJ_GAIN_MAX:-8.0} \
|
| 98 |
+
DA3_PERC_GAIN_INIT=${DA3_PERC_GAIN_INIT:-3.0} DA3_PERC_GAIN_MAX=${DA3_PERC_GAIN_MAX:-8.0} \
|
| 99 |
+
LR_VLM_PEAK=$LR_VLM_PEAK LR_VLM_END=$LR_VLM_END \
|
| 100 |
+
LR_CORE_PEAK=$LR_CORE_PEAK LR_CORE_END=$LR_CORE_END \
|
| 101 |
+
LR_GEOM_PEAK=$LR_GEOM_PEAK LR_GEOM_END=$LR_GEOM_END \
|
| 102 |
+
LR_GEOM_DELAY=$LR_GEOM_DELAY LR_GEOM_RAMP=$LR_GEOM_RAMP \
|
| 103 |
+
LR_VLM_DELAY=${LR_VLM_DELAY:-0} LR_VLM_RAMP=${LR_VLM_RAMP:-0} \
|
| 104 |
+
LR_VLM_CYCLE=${LR_VLM_CYCLE:-0} LR_VLM_FREEZE=${LR_VLM_FREEZE:-1000} LR_VLM_COADAPT=${LR_VLM_COADAPT:-500} LR_VLM_CYCLE_END=${LR_VLM_CYCLE_END:-10000} \
|
| 105 |
+
LR_CORE_DELAY=${LR_CORE_DELAY:-0} LR_CORE_RAMP=${LR_CORE_RAMP:-0} LR_CORE_RAMP_START=${LR_CORE_RAMP_START:-0.0} \
|
| 106 |
+
LR_VLM_DECAY_START=${LR_VLM_DECAY_START:-0} LR_CORE_DECAY_START=${LR_CORE_DECAY_START:-0} LR_GEOM_DECAY_START=${LR_GEOM_DECAY_START:-0} \
|
| 107 |
+
B1K_ACTIVITIES=$ACT \
|
| 108 |
+
B1K_INIT_PARAMS=${B1K_INIT_PARAMS:-/work/jack/behavior1k/checkpoints/behavior_50t_checkpoint/params} \
|
| 109 |
+
EXP=$EXP \
|
| 110 |
+
STEPS=$STEPS BS=$BS FLOW=15 NW=$NW SHUFFLE=1 RESUME=$RES OVERWRITE=$OVW \
|
| 111 |
+
SAVE_INTERVAL=2000 KEEP_PERIOD=${KEEP_PERIOD:-10000} LOG_INTERVAL=50 \
|
| 112 |
+
LR_WARMUP=$WARMUP LR_DECAY_STEPS=$LR_DECAY \
|
| 113 |
+
nohup $PIN .venv/bin/python scripts/train_2026.py >> "$LOG" 2>&1 &
|
| 114 |
+
echo "LAUNCHED pid $! ($MODE, $GMODE) BS=$BS STEPS=$STEPS LR_DECAY=$LR_DECAY numa=$PINNOTE -> $LOG"
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/openpi/src/openpi/training/data_loader.py
ADDED
|
@@ -0,0 +1,676 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import Iterator, Sequence
|
| 2 |
+
import logging
|
| 3 |
+
import multiprocessing
|
| 4 |
+
import os
|
| 5 |
+
import queue
|
| 6 |
+
import threading
|
| 7 |
+
import typing
|
| 8 |
+
from typing import Literal, Protocol, SupportsIndex, TypeVar
|
| 9 |
+
|
| 10 |
+
import jax
|
| 11 |
+
import jax.numpy as jnp
|
| 12 |
+
import lerobot.datasets.lerobot_dataset as lerobot_dataset
|
| 13 |
+
import numpy as np
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
import openpi.models.model as _model
|
| 17 |
+
import openpi.training.config as _config
|
| 18 |
+
from openpi.training.droid_rlds_dataset import DroidRldsDataset
|
| 19 |
+
import openpi.transforms as _transforms
|
| 20 |
+
|
| 21 |
+
T_co = TypeVar("T_co", covariant=True)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class Dataset(Protocol[T_co]):
|
| 25 |
+
"""Interface for a dataset with random access."""
|
| 26 |
+
|
| 27 |
+
def __getitem__(self, index: SupportsIndex) -> T_co:
|
| 28 |
+
raise NotImplementedError("Subclasses of Dataset should implement __getitem__.")
|
| 29 |
+
|
| 30 |
+
def __len__(self) -> int:
|
| 31 |
+
raise NotImplementedError("Subclasses of Dataset should implement __len__.")
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class IterableDataset(Protocol[T_co]):
|
| 35 |
+
"""Interface for an iterable dataset."""
|
| 36 |
+
|
| 37 |
+
def __iter__(self) -> Iterator[T_co]:
|
| 38 |
+
raise NotImplementedError("Subclasses of IterableDataset should implement __iter__.")
|
| 39 |
+
|
| 40 |
+
def __len__(self) -> int:
|
| 41 |
+
raise NotImplementedError("Subclasses of Dataset should implement __len__.")
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class DataLoader(Protocol[T_co]):
|
| 45 |
+
"""Interface for a data loader."""
|
| 46 |
+
|
| 47 |
+
def data_config(self) -> _config.DataConfig:
|
| 48 |
+
"""Get the data config for this data loader."""
|
| 49 |
+
raise NotImplementedError("Subclasses of DataLoader should implement data_config.")
|
| 50 |
+
|
| 51 |
+
def __iter__(self) -> Iterator[T_co]:
|
| 52 |
+
raise NotImplementedError("Subclasses of DataLoader should implement __iter__.")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class TransformedDataset(Dataset[T_co]):
|
| 56 |
+
def __init__(self, dataset: Dataset, transforms: Sequence[_transforms.DataTransformFn]):
|
| 57 |
+
self._dataset = dataset
|
| 58 |
+
self._transform = _transforms.compose(transforms)
|
| 59 |
+
|
| 60 |
+
def __getitem__(self, index: SupportsIndex) -> T_co:
|
| 61 |
+
return self._transform(self._dataset[index])
|
| 62 |
+
|
| 63 |
+
def __len__(self) -> int:
|
| 64 |
+
return len(self._dataset)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class IterableTransformedDataset(IterableDataset[T_co]):
|
| 68 |
+
def __init__(
|
| 69 |
+
self,
|
| 70 |
+
dataset: IterableDataset,
|
| 71 |
+
transforms: Sequence[_transforms.DataTransformFn],
|
| 72 |
+
*,
|
| 73 |
+
is_batched: bool = False,
|
| 74 |
+
):
|
| 75 |
+
self._dataset = dataset
|
| 76 |
+
self._transform = _transforms.compose(transforms)
|
| 77 |
+
self._is_batched = is_batched
|
| 78 |
+
|
| 79 |
+
def __iter__(self):
|
| 80 |
+
for sample in self._dataset:
|
| 81 |
+
if self._is_batched:
|
| 82 |
+
# Transforms are designed to be applied to individual samples. So we need to split the batch into
|
| 83 |
+
# individual samples and apply the transform to each sample individually.
|
| 84 |
+
batch_size = next(v.shape[0] for v in sample.values())
|
| 85 |
+
|
| 86 |
+
# Split batch into individual samples using tree_map
|
| 87 |
+
individual_samples = [jax.tree.map(lambda x: x[i], sample) for i in range(batch_size)] # noqa: B023
|
| 88 |
+
|
| 89 |
+
# Transform each sample
|
| 90 |
+
transformed = [self._transform(s) for s in individual_samples]
|
| 91 |
+
|
| 92 |
+
# Recombine batch with tree_map
|
| 93 |
+
yield jax.tree.map(lambda *x: np.stack(x, axis=0), *transformed)
|
| 94 |
+
else:
|
| 95 |
+
yield self._transform(sample)
|
| 96 |
+
|
| 97 |
+
def __len__(self) -> int:
|
| 98 |
+
return len(self._dataset)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
class FakeDataset(Dataset):
|
| 102 |
+
def __init__(self, model_config: _model.BaseModelConfig, num_samples: int):
|
| 103 |
+
self._num_samples = num_samples
|
| 104 |
+
self._observation_spec, self._action_spec = model_config.inputs_spec()
|
| 105 |
+
|
| 106 |
+
def __getitem__(self, index: SupportsIndex) -> dict:
|
| 107 |
+
rng = jax.random.key(index.__index__())
|
| 108 |
+
|
| 109 |
+
def make_from_spec(spec: jax.ShapeDtypeStruct):
|
| 110 |
+
nonlocal rng
|
| 111 |
+
rng, data_rng = jax.random.split(rng)
|
| 112 |
+
# Remove the batch dimension.
|
| 113 |
+
shape = spec.shape[1:]
|
| 114 |
+
if spec.dtype == jnp.float32:
|
| 115 |
+
return jax.random.uniform(data_rng, shape=shape, minval=-1.0, maxval=1.0)
|
| 116 |
+
if spec.dtype == jnp.int32:
|
| 117 |
+
return jax.random.randint(data_rng, shape=shape, minval=0, maxval=2048)
|
| 118 |
+
return jnp.zeros(shape=shape, dtype=spec.dtype)
|
| 119 |
+
|
| 120 |
+
observation = jax.tree.map(make_from_spec, self._observation_spec)
|
| 121 |
+
action = jax.tree.map(make_from_spec, self._action_spec)
|
| 122 |
+
|
| 123 |
+
return {
|
| 124 |
+
**observation.to_dict(),
|
| 125 |
+
"actions": action,
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
def __len__(self) -> int:
|
| 129 |
+
return self._num_samples
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def create_behavior_dataset(data_config: _config.DataConfig, action_horizon: int) -> Dataset:
|
| 133 |
+
"""Create a dataset for training."""
|
| 134 |
+
from omnigibson.learning.datas.lerobot_dataset import BehaviorLeRobotDataset
|
| 135 |
+
|
| 136 |
+
dataset = BehaviorLeRobotDataset(
|
| 137 |
+
repo_id=data_config.repo_id,
|
| 138 |
+
root=data_config.behavior_dataset_root,
|
| 139 |
+
tasks=["turning_on_radio"],
|
| 140 |
+
modalities=["rgb"],
|
| 141 |
+
local_only=True,
|
| 142 |
+
delta_timestamps={
|
| 143 |
+
key: [t / 30.0 for t in range(action_horizon)] for key in data_config.action_sequence_keys
|
| 144 |
+
},
|
| 145 |
+
episodes=data_config.episodes_index,
|
| 146 |
+
chunk_streaming_using_keyframe=True,
|
| 147 |
+
shuffle=True,
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
if data_config.prompt_from_task:
|
| 151 |
+
dataset = TransformedDataset(dataset, [_transforms.PromptFromLeRobotTask(dataset.meta.tasks)])
|
| 152 |
+
|
| 153 |
+
return dataset
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def create_torch_dataset(
|
| 157 |
+
data_config: _config.DataConfig, action_horizon: int, model_config: _model.BaseModelConfig
|
| 158 |
+
) -> Dataset:
|
| 159 |
+
"""Create a dataset for training."""
|
| 160 |
+
repo_id = data_config.repo_id
|
| 161 |
+
if repo_id is None:
|
| 162 |
+
raise ValueError("Repo ID is not set. Cannot create dataset.")
|
| 163 |
+
if repo_id == "fake":
|
| 164 |
+
return FakeDataset(model_config, num_samples=1024)
|
| 165 |
+
|
| 166 |
+
dataset_meta = lerobot_dataset.LeRobotDatasetMetadata(repo_id)
|
| 167 |
+
dataset = lerobot_dataset.LeRobotDataset(
|
| 168 |
+
data_config.repo_id,
|
| 169 |
+
delta_timestamps={
|
| 170 |
+
key: [t / dataset_meta.fps for t in range(action_horizon)] for key in data_config.action_sequence_keys
|
| 171 |
+
},
|
| 172 |
+
episodes=data_config.episodes_index,
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
if data_config.prompt_from_task:
|
| 176 |
+
dataset = TransformedDataset(dataset, [_transforms.PromptFromLeRobotTask(dataset_meta.tasks)])
|
| 177 |
+
|
| 178 |
+
return dataset
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def create_rlds_dataset(
|
| 182 |
+
data_config: _config.DataConfig,
|
| 183 |
+
action_horizon: int,
|
| 184 |
+
batch_size: int,
|
| 185 |
+
*,
|
| 186 |
+
shuffle: bool = False,
|
| 187 |
+
) -> Dataset:
|
| 188 |
+
# At the moment, we only support DROID for RLDS datasets.
|
| 189 |
+
return DroidRldsDataset(
|
| 190 |
+
data_dir=data_config.rlds_data_dir,
|
| 191 |
+
batch_size=batch_size,
|
| 192 |
+
shuffle=shuffle,
|
| 193 |
+
action_chunk_size=action_horizon,
|
| 194 |
+
action_space=data_config.action_space,
|
| 195 |
+
filter_dict_path=data_config.filter_dict_path,
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def transform_dataset(dataset: Dataset, data_config: _config.DataConfig, *, skip_norm_stats: bool = False) -> Dataset:
|
| 200 |
+
"""Transform the dataset by applying the data transforms."""
|
| 201 |
+
norm_stats = {}
|
| 202 |
+
if data_config.repo_id != "fake" and not skip_norm_stats:
|
| 203 |
+
if data_config.norm_stats is None:
|
| 204 |
+
raise ValueError(
|
| 205 |
+
"Normalization stats not found. "
|
| 206 |
+
"Make sure to run `scripts/compute_norm_stats.py --config-name=<your-config>`."
|
| 207 |
+
)
|
| 208 |
+
norm_stats = data_config.norm_stats
|
| 209 |
+
|
| 210 |
+
return TransformedDataset(
|
| 211 |
+
dataset,
|
| 212 |
+
[
|
| 213 |
+
*data_config.repack_transforms.inputs,
|
| 214 |
+
*data_config.data_transforms.inputs,
|
| 215 |
+
_transforms.Normalize(norm_stats, use_quantiles=data_config.use_quantile_norm),
|
| 216 |
+
*data_config.model_transforms.inputs,
|
| 217 |
+
],
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def transform_iterable_dataset(
|
| 222 |
+
dataset: IterableDataset,
|
| 223 |
+
data_config: _config.DataConfig,
|
| 224 |
+
*,
|
| 225 |
+
skip_norm_stats: bool = False,
|
| 226 |
+
is_batched: bool = False,
|
| 227 |
+
) -> IterableDataset:
|
| 228 |
+
"""Transform the dataset by applying the data transforms."""
|
| 229 |
+
norm_stats = {}
|
| 230 |
+
if data_config.repo_id != "fake" and not skip_norm_stats:
|
| 231 |
+
if data_config.norm_stats is None:
|
| 232 |
+
raise ValueError(
|
| 233 |
+
"Normalization stats not found. "
|
| 234 |
+
"Make sure to run `scripts/compute_norm_stats.py --config-name=<your-config>`."
|
| 235 |
+
)
|
| 236 |
+
norm_stats = data_config.norm_stats
|
| 237 |
+
|
| 238 |
+
return IterableTransformedDataset(
|
| 239 |
+
dataset,
|
| 240 |
+
[
|
| 241 |
+
*data_config.repack_transforms.inputs,
|
| 242 |
+
*data_config.data_transforms.inputs,
|
| 243 |
+
_transforms.Normalize(norm_stats, use_quantiles=data_config.use_quantile_norm),
|
| 244 |
+
*data_config.model_transforms.inputs,
|
| 245 |
+
],
|
| 246 |
+
is_batched=is_batched,
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def create_data_loader(
|
| 251 |
+
config: _config.TrainConfig,
|
| 252 |
+
*,
|
| 253 |
+
sharding: jax.sharding.Sharding | None = None,
|
| 254 |
+
shuffle: bool = False,
|
| 255 |
+
num_batches: int | None = None,
|
| 256 |
+
skip_norm_stats: bool = False,
|
| 257 |
+
framework: Literal["jax", "pytorch"] = "jax",
|
| 258 |
+
) -> DataLoader[tuple[_model.Observation, _model.Actions]]:
|
| 259 |
+
"""Create a data loader for training.
|
| 260 |
+
|
| 261 |
+
Args:
|
| 262 |
+
config: The training configuration.
|
| 263 |
+
sharding: The sharding to use for the data loader (JAX only).
|
| 264 |
+
shuffle: Whether to shuffle the data.
|
| 265 |
+
num_batches: Determines the number of batches to return.
|
| 266 |
+
skip_norm_stats: Whether to skip data normalization.
|
| 267 |
+
framework: The framework to use ("jax" or "pytorch").
|
| 268 |
+
"""
|
| 269 |
+
data_config = config.data.create(config.assets_dirs, config.model)
|
| 270 |
+
logging.info(f"data_config: {data_config}")
|
| 271 |
+
|
| 272 |
+
if data_config.rlds_data_dir is not None:
|
| 273 |
+
return create_rlds_data_loader(
|
| 274 |
+
data_config,
|
| 275 |
+
action_horizon=config.model.action_horizon,
|
| 276 |
+
batch_size=config.batch_size,
|
| 277 |
+
sharding=sharding,
|
| 278 |
+
shuffle=shuffle,
|
| 279 |
+
num_batches=num_batches,
|
| 280 |
+
skip_norm_stats=skip_norm_stats,
|
| 281 |
+
framework=framework,
|
| 282 |
+
)
|
| 283 |
+
return create_torch_data_loader(
|
| 284 |
+
data_config,
|
| 285 |
+
model_config=config.model,
|
| 286 |
+
action_horizon=config.model.action_horizon,
|
| 287 |
+
batch_size=config.batch_size,
|
| 288 |
+
sharding=sharding,
|
| 289 |
+
shuffle=shuffle,
|
| 290 |
+
num_batches=num_batches,
|
| 291 |
+
num_workers=config.num_workers,
|
| 292 |
+
seed=config.seed,
|
| 293 |
+
skip_norm_stats=skip_norm_stats,
|
| 294 |
+
framework=framework,
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def create_behavior_data_loader(
|
| 299 |
+
config: _config.TrainConfig,
|
| 300 |
+
*,
|
| 301 |
+
sharding: jax.sharding.Sharding | None = None,
|
| 302 |
+
shuffle: bool = False,
|
| 303 |
+
num_batches: int | None = None,
|
| 304 |
+
skip_norm_stats: bool = False,
|
| 305 |
+
) -> DataLoader[tuple[_model.Observation, _model.Actions]]:
|
| 306 |
+
data_config = config.data.create(config.assets_dirs, config.model)
|
| 307 |
+
dataset = create_behavior_dataset(data_config, action_horizon=config.model.action_horizon)
|
| 308 |
+
dataset = transform_dataset(dataset, data_config, skip_norm_stats=skip_norm_stats)
|
| 309 |
+
|
| 310 |
+
data_loader = TorchDataLoader(
|
| 311 |
+
dataset,
|
| 312 |
+
local_batch_size=config.batch_size // jax.process_count(),
|
| 313 |
+
sharding=sharding,
|
| 314 |
+
shuffle=shuffle,
|
| 315 |
+
num_batches=num_batches,
|
| 316 |
+
num_workers=config.num_workers,
|
| 317 |
+
seed=config.seed,
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
return DataLoaderImpl(data_config, data_loader)
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def create_torch_data_loader(
|
| 324 |
+
data_config: _config.DataConfig,
|
| 325 |
+
model_config: _model.BaseModelConfig,
|
| 326 |
+
action_horizon: int,
|
| 327 |
+
batch_size: int,
|
| 328 |
+
*,
|
| 329 |
+
sharding: jax.sharding.Sharding | None = None,
|
| 330 |
+
skip_norm_stats: bool = False,
|
| 331 |
+
shuffle: bool = False,
|
| 332 |
+
num_batches: int | None = None,
|
| 333 |
+
num_workers: int = 0,
|
| 334 |
+
seed: int = 0,
|
| 335 |
+
framework: str = "jax",
|
| 336 |
+
) -> DataLoader[tuple[_model.Observation, _model.Actions]]:
|
| 337 |
+
"""Create a data loader for training.
|
| 338 |
+
|
| 339 |
+
Args:
|
| 340 |
+
data_config: The data configuration.
|
| 341 |
+
action_horizon: The action horizon.
|
| 342 |
+
batch_size: The batch size.
|
| 343 |
+
sharding: The sharding to use for the data loader. If None, the data loader will
|
| 344 |
+
use a single device sharding.
|
| 345 |
+
skip_norm_stats: Whether to skip data normalization.
|
| 346 |
+
shuffle: Whether to shuffle the data.
|
| 347 |
+
num_batches: Determines the number of batches to return. If the number exceeds the
|
| 348 |
+
number of batches in the dataset, the data loader will loop over the dataset.
|
| 349 |
+
If not provided, will iterate over the dataset indefinitely.
|
| 350 |
+
num_workers: The number of worker processes to use. If zero, the data loader will
|
| 351 |
+
execute in the main process.
|
| 352 |
+
seed: The seed to use for shuffling the data.
|
| 353 |
+
"""
|
| 354 |
+
dataset = create_torch_dataset(data_config, action_horizon, model_config)
|
| 355 |
+
dataset = transform_dataset(dataset, data_config, skip_norm_stats=skip_norm_stats)
|
| 356 |
+
|
| 357 |
+
# Use TorchDataLoader for both frameworks
|
| 358 |
+
# For PyTorch DDP, create DistributedSampler and divide batch size by world size
|
| 359 |
+
# For JAX, divide by process count
|
| 360 |
+
sampler = None
|
| 361 |
+
if framework == "pytorch":
|
| 362 |
+
if torch.distributed.is_initialized():
|
| 363 |
+
sampler = torch.utils.data.distributed.DistributedSampler(
|
| 364 |
+
dataset,
|
| 365 |
+
num_replicas=torch.distributed.get_world_size(),
|
| 366 |
+
rank=torch.distributed.get_rank(),
|
| 367 |
+
shuffle=shuffle,
|
| 368 |
+
drop_last=True,
|
| 369 |
+
)
|
| 370 |
+
local_batch_size = batch_size // torch.distributed.get_world_size()
|
| 371 |
+
else:
|
| 372 |
+
local_batch_size = batch_size
|
| 373 |
+
else:
|
| 374 |
+
local_batch_size = batch_size // jax.process_count()
|
| 375 |
+
|
| 376 |
+
logging.info(f"local_batch_size: {local_batch_size}")
|
| 377 |
+
data_loader = TorchDataLoader(
|
| 378 |
+
dataset,
|
| 379 |
+
local_batch_size=local_batch_size,
|
| 380 |
+
sharding=None if framework == "pytorch" else sharding,
|
| 381 |
+
shuffle=(sampler is None and shuffle), # Don't shuffle if using sampler
|
| 382 |
+
sampler=sampler,
|
| 383 |
+
num_batches=num_batches,
|
| 384 |
+
num_workers=num_workers,
|
| 385 |
+
seed=seed,
|
| 386 |
+
framework=framework,
|
| 387 |
+
)
|
| 388 |
+
|
| 389 |
+
return DataLoaderImpl(data_config, data_loader)
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
def create_rlds_data_loader(
|
| 393 |
+
data_config: _config.DataConfig,
|
| 394 |
+
action_horizon: int,
|
| 395 |
+
batch_size: int,
|
| 396 |
+
*,
|
| 397 |
+
sharding: jax.sharding.Sharding | None = None,
|
| 398 |
+
skip_norm_stats: bool = False,
|
| 399 |
+
shuffle: bool = False,
|
| 400 |
+
num_batches: int | None = None,
|
| 401 |
+
framework: str = "jax",
|
| 402 |
+
) -> DataLoader[tuple[_model.Observation, _model.Actions]]:
|
| 403 |
+
"""Create an RLDS data loader for training.
|
| 404 |
+
|
| 405 |
+
Note: This data loader requires some extra dependencies -- see examples/droid/README_train.md
|
| 406 |
+
|
| 407 |
+
Args:
|
| 408 |
+
data_config: The data configuration.
|
| 409 |
+
action_horizon: The action horizon.
|
| 410 |
+
batch_size: The batch size.
|
| 411 |
+
sharding: The sharding to use for the data loader. If None, the data loader will
|
| 412 |
+
use a single device sharding.
|
| 413 |
+
skip_norm_stats: Whether to skip data normalization.
|
| 414 |
+
shuffle: Whether to shuffle the data.
|
| 415 |
+
num_batches: Determines the number of batches to return. If the number exceeds the
|
| 416 |
+
number of batches in the dataset, the data loader will loop over the dataset.
|
| 417 |
+
If not provided, will iterate over the dataset indefinitely.
|
| 418 |
+
"""
|
| 419 |
+
if framework == "pytorch":
|
| 420 |
+
raise NotImplementedError("PyTorch RLDS data loader is not supported yet")
|
| 421 |
+
dataset = create_rlds_dataset(data_config, action_horizon, batch_size, shuffle=shuffle)
|
| 422 |
+
dataset = transform_iterable_dataset(dataset, data_config, skip_norm_stats=skip_norm_stats, is_batched=True)
|
| 423 |
+
|
| 424 |
+
data_loader = RLDSDataLoader(
|
| 425 |
+
dataset,
|
| 426 |
+
sharding=sharding,
|
| 427 |
+
num_batches=num_batches,
|
| 428 |
+
)
|
| 429 |
+
|
| 430 |
+
return DataLoaderImpl(data_config, data_loader)
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
class TorchDataLoader:
|
| 434 |
+
"""Torch data loader implementation."""
|
| 435 |
+
|
| 436 |
+
def __init__(
|
| 437 |
+
self,
|
| 438 |
+
dataset,
|
| 439 |
+
local_batch_size: int,
|
| 440 |
+
*,
|
| 441 |
+
sharding: jax.sharding.Sharding | None = None,
|
| 442 |
+
shuffle: bool = False,
|
| 443 |
+
sampler: torch.utils.data.Sampler | None = None,
|
| 444 |
+
num_batches: int | None = None,
|
| 445 |
+
num_workers: int = 0,
|
| 446 |
+
seed: int = 0,
|
| 447 |
+
framework: str = "jax",
|
| 448 |
+
batch_transform=None,
|
| 449 |
+
):
|
| 450 |
+
"""Create a PyTorch data loader.
|
| 451 |
+
|
| 452 |
+
Args:
|
| 453 |
+
dataset: The dataset to load.
|
| 454 |
+
local_batch_size: The local batch size for each process.
|
| 455 |
+
sharding: The sharding to use for the data loader.
|
| 456 |
+
shuffle: Whether to shuffle the data.
|
| 457 |
+
num_batches: If provided, determines the number of returned batches. If the
|
| 458 |
+
number is larger than the number of batches in the dataset, the data loader
|
| 459 |
+
will loop over the dataset. If not provided, will iterate over the dataset
|
| 460 |
+
indefinitely.
|
| 461 |
+
num_workers: The number of worker processes to use. If zero, the data loader will
|
| 462 |
+
execute in the main process.
|
| 463 |
+
seed: The seed to use for shuffling the data.
|
| 464 |
+
"""
|
| 465 |
+
if jax.process_count() > 1:
|
| 466 |
+
raise NotImplementedError("Data loading with multiple processes is not supported.")
|
| 467 |
+
|
| 468 |
+
if len(dataset) < local_batch_size:
|
| 469 |
+
raise ValueError(f"Local batch size ({local_batch_size}) is larger than the dataset size ({len(dataset)}).")
|
| 470 |
+
|
| 471 |
+
# Store sharding - None for PyTorch, JAX sharding for JAX
|
| 472 |
+
self._sharding = sharding
|
| 473 |
+
if sharding is None and framework == "jax":
|
| 474 |
+
# Use data parallel sharding by default for JAX only.
|
| 475 |
+
self._sharding = jax.sharding.NamedSharding(
|
| 476 |
+
jax.sharding.Mesh(jax.devices(), ("B",)),
|
| 477 |
+
jax.sharding.PartitionSpec("B"),
|
| 478 |
+
)
|
| 479 |
+
self._num_batches = num_batches
|
| 480 |
+
# optional per-batch hook applied to the (numpy) batch before sharding — used for inline
|
| 481 |
+
# DA3 feature extraction (runs the frozen GIANT on the batch's raw frames on GPU).
|
| 482 |
+
self._batch_transform = batch_transform
|
| 483 |
+
|
| 484 |
+
mp_context = None
|
| 485 |
+
if num_workers > 0:
|
| 486 |
+
mp_context = multiprocessing.get_context("spawn")
|
| 487 |
+
|
| 488 |
+
generator = torch.Generator()
|
| 489 |
+
generator.manual_seed(seed)
|
| 490 |
+
data_loader_kwargs = {}
|
| 491 |
+
if num_workers > 0:
|
| 492 |
+
prefetch_factor = int(os.environ.get("B1K_PREFETCH_FACTOR", "2"))
|
| 493 |
+
if prefetch_factor > 0:
|
| 494 |
+
data_loader_kwargs["prefetch_factor"] = prefetch_factor
|
| 495 |
+
if _TORCH_COLLATE:
|
| 496 |
+
# Only meaningful for torch tensors; pins in the loader's pin thread so the
|
| 497 |
+
# extractor's H2D can be a true async DMA (non_blocking) instead of a pageable copy.
|
| 498 |
+
data_loader_kwargs["pin_memory"] = True
|
| 499 |
+
self._data_loader = torch.utils.data.DataLoader(
|
| 500 |
+
typing.cast(torch.utils.data.Dataset, dataset),
|
| 501 |
+
batch_size=local_batch_size,
|
| 502 |
+
shuffle=(sampler is None and shuffle), # Don't shuffle if using sampler
|
| 503 |
+
sampler=sampler,
|
| 504 |
+
num_workers=num_workers,
|
| 505 |
+
multiprocessing_context=mp_context,
|
| 506 |
+
persistent_workers=num_workers > 0,
|
| 507 |
+
collate_fn=_collate_fn,
|
| 508 |
+
worker_init_fn=_worker_init_fn,
|
| 509 |
+
drop_last=True,
|
| 510 |
+
generator=generator,
|
| 511 |
+
**data_loader_kwargs,
|
| 512 |
+
)
|
| 513 |
+
|
| 514 |
+
@property
|
| 515 |
+
def torch_loader(self) -> torch.utils.data.DataLoader:
|
| 516 |
+
return self._data_loader
|
| 517 |
+
|
| 518 |
+
def _transformed_batches(self):
|
| 519 |
+
"""Yields batch_transform-ed batches, looping over the dataset indefinitely.
|
| 520 |
+
|
| 521 |
+
When a batch_transform is set (inline DA3 GPU extraction), it runs ONE BATCH AHEAD in a
|
| 522 |
+
background thread, so the frozen DA3 forward for batch N+1 overlaps the train step for
|
| 523 |
+
batch N instead of serializing with it (torch GPU kernels release the GIL).
|
| 524 |
+
"""
|
| 525 |
+
def epochs():
|
| 526 |
+
epoch = 0
|
| 527 |
+
sampler = getattr(self._data_loader, "sampler", None)
|
| 528 |
+
while True:
|
| 529 |
+
if hasattr(sampler, "set_epoch"):
|
| 530 |
+
sampler.set_epoch(epoch)
|
| 531 |
+
yield from self._data_loader
|
| 532 |
+
epoch += 1
|
| 533 |
+
|
| 534 |
+
if self._batch_transform is None:
|
| 535 |
+
yield from epochs()
|
| 536 |
+
return
|
| 537 |
+
|
| 538 |
+
# Depth = how many extracted batches can be buffered ahead of training. Default 2 (unchanged).
|
| 539 |
+
# On a dedicated-extraction-GPU setup (extract on spare GPUs, train on others) bump this via
|
| 540 |
+
# B1K_EXTRACT_QUEUE=3-4 so extraction runs further ahead and smooths over per-batch variance.
|
| 541 |
+
q: queue.Queue = queue.Queue(maxsize=int(os.environ.get("B1K_EXTRACT_QUEUE", "2")))
|
| 542 |
+
|
| 543 |
+
def producer():
|
| 544 |
+
try:
|
| 545 |
+
for raw in epochs():
|
| 546 |
+
b = self._batch_transform(raw)
|
| 547 |
+
# Stage the REMAINING host fields (base RGB / actions / state) onto the training
|
| 548 |
+
# GPUs here in the producer instead of on the main thread at consume time. Same
|
| 549 |
+
# make_array, just moved off the train loop's critical path so the host->GPU copy
|
| 550 |
+
# overlaps the in-flight train step. (DA3 fields are already jax.Array via DLPack.)
|
| 551 |
+
if self._sharding is not None:
|
| 552 |
+
b = jax.tree.map(
|
| 553 |
+
lambda x: x if isinstance(x, jax.Array)
|
| 554 |
+
else jax.make_array_from_process_local_data(self._sharding, _host_np(x)), b)
|
| 555 |
+
q.put(b)
|
| 556 |
+
except BaseException as e: # noqa: BLE001 — propagate any failure to the consumer
|
| 557 |
+
q.put(e)
|
| 558 |
+
|
| 559 |
+
threading.Thread(target=producer, daemon=True, name="batch-transform-prefetch").start()
|
| 560 |
+
while True:
|
| 561 |
+
item = q.get()
|
| 562 |
+
if isinstance(item, BaseException):
|
| 563 |
+
raise item
|
| 564 |
+
yield item
|
| 565 |
+
|
| 566 |
+
def __iter__(self):
|
| 567 |
+
num_items = 0
|
| 568 |
+
batches = self._transformed_batches()
|
| 569 |
+
while True:
|
| 570 |
+
# Check BEFORE pulling: the generator prefetches, and pulling past num_batches would
|
| 571 |
+
# trigger (and discard) a whole extra batch across the epoch boundary.
|
| 572 |
+
if self._num_batches is not None and num_items >= self._num_batches:
|
| 573 |
+
return
|
| 574 |
+
batch = next(batches)
|
| 575 |
+
num_items += 1
|
| 576 |
+
# For JAX, convert to sharded arrays; for PyTorch, return torch tensors.
|
| 577 |
+
# Fields already placed as jax.Array (DLPack GPU->GPU handoff) pass through untouched.
|
| 578 |
+
if self._sharding is not None:
|
| 579 |
+
yield jax.tree.map(
|
| 580 |
+
lambda x: x if isinstance(x, jax.Array)
|
| 581 |
+
else jax.make_array_from_process_local_data(self._sharding, _host_np(x)), batch)
|
| 582 |
+
else:
|
| 583 |
+
yield jax.tree.map(torch.as_tensor, batch)
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
_TORCH_COLLATE = os.environ.get("B1K_TORCH_COLLATE") == "1"
|
| 587 |
+
|
| 588 |
+
|
| 589 |
+
def _host_np(x):
|
| 590 |
+
"""torch CPU tensor -> numpy (zero-copy view); pass anything else through unchanged."""
|
| 591 |
+
return x.numpy() if isinstance(x, torch.Tensor) else x
|
| 592 |
+
|
| 593 |
+
|
| 594 |
+
def _collate_fn(items):
|
| 595 |
+
"""Collate the batch elements into batched arrays.
|
| 596 |
+
|
| 597 |
+
With B1K_TORCH_COLLATE=1 the batch is built as torch tensors instead of numpy. The DataLoader
|
| 598 |
+
hands torch tensors worker->main through SHARED MEMORY (fd passing) rather than pickling and
|
| 599 |
+
copying them, which is a real saving at ~131MB/batch, and it makes pin_memory (hence async
|
| 600 |
+
non_blocking H2D) possible. Values are bit-identical either way.
|
| 601 |
+
"""
|
| 602 |
+
if _TORCH_COLLATE:
|
| 603 |
+
def _stack(*xs):
|
| 604 |
+
a = [np.asarray(x) for x in xs]
|
| 605 |
+
if a[0].dtype.kind in "biufc": # numeric -> torch tensor (shared-memory IPC + pinnable)
|
| 606 |
+
return torch.stack([torch.as_tensor(v) for v in a], 0)
|
| 607 |
+
return np.stack(a, axis=0) # strings/objects have no torch equivalent; keep numpy
|
| 608 |
+
return jax.tree.map(_stack, *items)
|
| 609 |
+
# Make sure to convert to numpy arrays before stacking since some of the incoming elements
|
| 610 |
+
# may be JAX arrays.
|
| 611 |
+
return jax.tree.map(lambda *xs: np.stack([np.asarray(x) for x in xs], axis=0), *items)
|
| 612 |
+
|
| 613 |
+
|
| 614 |
+
def _worker_init_fn(worker_id: int) -> None:
|
| 615 |
+
"""Tell JAX inside the worker process not to preallocate the GPU memory."""
|
| 616 |
+
# NOTE: This is called after jax is imported inside the worker process. This
|
| 617 |
+
# means that this approach will not work for selecting the backend.
|
| 618 |
+
os.environ["XLA_PYTHON_CLIENT_PREALLOCATE"] = "false"
|
| 619 |
+
os.environ["XLA_PYTHON_CLIENT_ALLOCATOR"] = "platform"
|
| 620 |
+
|
| 621 |
+
|
| 622 |
+
class RLDSDataLoader:
|
| 623 |
+
"""Shallow wrapper around the DROID data loader to make it compatible with openpi.
|
| 624 |
+
|
| 625 |
+
All batching already happens in the DROID dataset, so we don't need to do anything here.
|
| 626 |
+
"""
|
| 627 |
+
|
| 628 |
+
def __init__(
|
| 629 |
+
self,
|
| 630 |
+
dataset: DroidRldsDataset,
|
| 631 |
+
*,
|
| 632 |
+
sharding: jax.sharding.Sharding | None = None,
|
| 633 |
+
num_batches: int | None = None,
|
| 634 |
+
):
|
| 635 |
+
self._dataset = dataset
|
| 636 |
+
self._num_batches = num_batches
|
| 637 |
+
|
| 638 |
+
if jax.process_count() > 1:
|
| 639 |
+
raise NotImplementedError("Data loading with multiple processes is not supported.")
|
| 640 |
+
|
| 641 |
+
if sharding is None:
|
| 642 |
+
# Use data parallel sharding by default.
|
| 643 |
+
sharding = jax.sharding.NamedSharding(
|
| 644 |
+
jax.sharding.Mesh(jax.devices(), ("B",)),
|
| 645 |
+
jax.sharding.PartitionSpec("B"),
|
| 646 |
+
)
|
| 647 |
+
|
| 648 |
+
self._sharding = sharding
|
| 649 |
+
self._num_batches = num_batches
|
| 650 |
+
|
| 651 |
+
def __iter__(self):
|
| 652 |
+
num_items = 0
|
| 653 |
+
while True:
|
| 654 |
+
data_iter = iter(self._dataset)
|
| 655 |
+
while True:
|
| 656 |
+
if self._num_batches is not None and num_items >= self._num_batches:
|
| 657 |
+
return
|
| 658 |
+
try:
|
| 659 |
+
batch = next(data_iter)
|
| 660 |
+
except StopIteration:
|
| 661 |
+
break # We've exhausted the dataset. Create a new iterator and start over.
|
| 662 |
+
num_items += 1
|
| 663 |
+
yield jax.tree.map(lambda x: jax.make_array_from_process_local_data(self._sharding, x), batch)
|
| 664 |
+
|
| 665 |
+
|
| 666 |
+
class DataLoaderImpl(DataLoader):
|
| 667 |
+
def __init__(self, data_config: _config.DataConfig, data_loader: TorchDataLoader | RLDSDataLoader):
|
| 668 |
+
self._data_config = data_config
|
| 669 |
+
self._data_loader = data_loader
|
| 670 |
+
|
| 671 |
+
def data_config(self) -> _config.DataConfig:
|
| 672 |
+
return self._data_config
|
| 673 |
+
|
| 674 |
+
def __iter__(self):
|
| 675 |
+
for batch in self._data_loader:
|
| 676 |
+
yield _model.Observation.from_dict(batch), batch["actions"]
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/scripts/serve_b1k.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import dataclasses
|
| 2 |
+
import enum
|
| 3 |
+
import logging
|
| 4 |
+
import os
|
| 5 |
+
import pathlib
|
| 6 |
+
import socket
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import tyro
|
| 10 |
+
|
| 11 |
+
# Set JAX memory allocation before importing JAX (can be overridden by env vars)
|
| 12 |
+
os.environ.setdefault('XLA_PYTHON_CLIENT_MEM_FRACTION', '0.5') # Use 50% of GPU memory
|
| 13 |
+
os.environ.setdefault('XLA_PYTHON_CLIENT_ALLOCATOR', 'platform') # Platform allocator
|
| 14 |
+
|
| 15 |
+
from omnigibson.learning.utils.network_utils import WebsocketPolicyServer
|
| 16 |
+
from omnigibson.learning.datas import BehaviorLerobotDatasetMetadata
|
| 17 |
+
|
| 18 |
+
from openpi.policies import policy as _policy
|
| 19 |
+
|
| 20 |
+
# Import B1K-specific modules
|
| 21 |
+
from b1k.policies import policy_config as _policy_config # Use our custom policy_config
|
| 22 |
+
from b1k.policies.checkpoint_switcher import CheckpointSwitcher
|
| 23 |
+
from b1k.shared.eval_b1k_wrapper import B1KPolicyWrapper, B1KWrapperConfig
|
| 24 |
+
from b1k.training import config as _config
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class EnvMode(enum.Enum):
|
| 28 |
+
# Not used, just kept for compatibility
|
| 29 |
+
ALOHA = "aloha"
|
| 30 |
+
ALOHA_SIM = "aloha_sim"
|
| 31 |
+
DROID = "droid"
|
| 32 |
+
LIBERO = "libero"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@dataclasses.dataclass
|
| 36 |
+
class Checkpoint:
|
| 37 |
+
"""Load a policy from a trained checkpoint."""
|
| 38 |
+
config: str
|
| 39 |
+
dir: str
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@dataclasses.dataclass
|
| 43 |
+
class Default:
|
| 44 |
+
"""Use the default policy for the given environment."""
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@dataclasses.dataclass
|
| 48 |
+
class Args:
|
| 49 |
+
"""Arguments for the serve_policy script."""
|
| 50 |
+
|
| 51 |
+
# Environment to serve the policy for. This is only used when serving default policies.
|
| 52 |
+
env: EnvMode = EnvMode.ALOHA_SIM
|
| 53 |
+
|
| 54 |
+
# If provided, will be used in case the "prompt" key is not present in the data, or if the model doesn't have a default prompt.
|
| 55 |
+
default_prompt: str | None = None
|
| 56 |
+
|
| 57 |
+
# For PI_BEHAVIOR models: task ID (0-49) instead of text prompt
|
| 58 |
+
task_id: int | None = None
|
| 59 |
+
|
| 60 |
+
# Dataset root, used to retrieve the prompt of the task if taskname is not None.
|
| 61 |
+
dataset_root: str | None = "/scr/behavior/2025-challenge-demos"
|
| 62 |
+
# If provided, will be used to retrieve the prompt of the task, otherwise use turning_on_radio as default.
|
| 63 |
+
task_name: str | None = None
|
| 64 |
+
|
| 65 |
+
# Port to serve the policy on.
|
| 66 |
+
port: int = 8000
|
| 67 |
+
# Record the policy's behavior for debugging.
|
| 68 |
+
record: bool = False
|
| 69 |
+
|
| 70 |
+
# Specifies how to load the policy. If not provided, the default policy for the environment will be used.
|
| 71 |
+
policy: Checkpoint | Default = dataclasses.field(default_factory=Default)
|
| 72 |
+
|
| 73 |
+
# B1K Wrapper execution parameters
|
| 74 |
+
actions_to_execute: int = 26
|
| 75 |
+
actions_to_keep: int = 4
|
| 76 |
+
execute_in_n_steps: int = 20
|
| 77 |
+
history_len: int = 3
|
| 78 |
+
votes_to_promote: int = 2
|
| 79 |
+
time_threshold_inpaint: float = 0.3
|
| 80 |
+
num_steps: int = 20
|
| 81 |
+
apply_eval_tricks: bool = True # Enable correction rules and gripper variation checks
|
| 82 |
+
|
| 83 |
+
# Multi-checkpoint support for PI_BEHAVIOR models (optional)
|
| 84 |
+
task_checkpoint_mapping: str | None = None # Path to task-checkpoint mapping JSON file
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def create_policy(args: Args) -> _policy.Policy:
|
| 88 |
+
"""Create a policy from the given arguments."""
|
| 89 |
+
sample_kwargs = {"num_steps": args.num_steps}
|
| 90 |
+
return _policy_config.create_trained_policy(
|
| 91 |
+
_config.get_config(args.policy.config),
|
| 92 |
+
args.policy.dir,
|
| 93 |
+
default_prompt=args.default_prompt,
|
| 94 |
+
sample_kwargs=sample_kwargs
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def main(args: Args) -> None:
|
| 99 |
+
# B1K only supports PI_BEHAVIOR models (task embeddings, no text prompts)
|
| 100 |
+
config = _config.get_config(args.policy.config)
|
| 101 |
+
|
| 102 |
+
# PI_BEHAVIOR model setup
|
| 103 |
+
if args.task_id is not None:
|
| 104 |
+
logging.info(f"Using PI_BEHAVIOR model with task_id: {args.task_id}")
|
| 105 |
+
task_id = args.task_id
|
| 106 |
+
else:
|
| 107 |
+
logging.info(f"Using PI_BEHAVIOR model - task_id will be extracted from observations")
|
| 108 |
+
task_id = None
|
| 109 |
+
|
| 110 |
+
# Placeholder prompt for PI_BEHAVIOR (not actually used by model)
|
| 111 |
+
prompt = "PI_BEHAVIOR model (task-conditioned)"
|
| 112 |
+
logging.info(f"Using prompt: {prompt}")
|
| 113 |
+
|
| 114 |
+
# Load initial/default policy
|
| 115 |
+
policy = create_policy(args)
|
| 116 |
+
policy_metadata = policy.metadata
|
| 117 |
+
|
| 118 |
+
# Create checkpoint switcher if mapping file provided
|
| 119 |
+
checkpoint_switcher = None
|
| 120 |
+
if args.task_checkpoint_mapping:
|
| 121 |
+
logging.info(f"Multi-checkpoint mode enabled: {args.task_checkpoint_mapping}")
|
| 122 |
+
|
| 123 |
+
sample_kwargs = {"num_steps": args.num_steps}
|
| 124 |
+
|
| 125 |
+
try:
|
| 126 |
+
checkpoint_switcher = CheckpointSwitcher(
|
| 127 |
+
config_path=args.task_checkpoint_mapping,
|
| 128 |
+
training_config=config,
|
| 129 |
+
sample_kwargs=sample_kwargs
|
| 130 |
+
)
|
| 131 |
+
logging.info("Checkpoint switcher initialized - will switch checkpoints based on task_id")
|
| 132 |
+
except Exception as e:
|
| 133 |
+
logging.error(f"Failed to initialize checkpoint switcher: {e}")
|
| 134 |
+
raise
|
| 135 |
+
else:
|
| 136 |
+
logging.info("Single checkpoint mode - using one checkpoint for all tasks")
|
| 137 |
+
|
| 138 |
+
# Record the policy's behavior.
|
| 139 |
+
if args.record:
|
| 140 |
+
policy = _policy.PolicyRecorder(policy, "policy_records")
|
| 141 |
+
|
| 142 |
+
# Create wrapper configuration
|
| 143 |
+
wrapper_config = B1KWrapperConfig(
|
| 144 |
+
actions_to_execute=args.actions_to_execute,
|
| 145 |
+
actions_to_keep=args.actions_to_keep,
|
| 146 |
+
execute_in_n_steps=args.execute_in_n_steps,
|
| 147 |
+
history_len=args.history_len,
|
| 148 |
+
votes_to_promote=args.votes_to_promote,
|
| 149 |
+
time_threshold_inpaint=args.time_threshold_inpaint,
|
| 150 |
+
num_steps=args.num_steps,
|
| 151 |
+
apply_eval_tricks=args.apply_eval_tricks,
|
| 152 |
+
)
|
| 153 |
+
|
| 154 |
+
logging.info(f"Wrapper config: execute={wrapper_config.actions_to_execute}, keep={wrapper_config.actions_to_keep}, steps={wrapper_config.execute_in_n_steps}, num_steps={wrapper_config.num_steps}")
|
| 155 |
+
|
| 156 |
+
if wrapper_config.apply_eval_tricks:
|
| 157 |
+
logging.info("Eval tricks ENABLED - correction rules and gripper variation checks active")
|
| 158 |
+
else:
|
| 159 |
+
logging.info("Eval tricks DISABLED (default behavior)")
|
| 160 |
+
|
| 161 |
+
# Create B1K wrapper with PI_BEHAVIOR-specific features
|
| 162 |
+
policy = B1KPolicyWrapper(
|
| 163 |
+
policy,
|
| 164 |
+
text_prompt=prompt, # Not used by PI_BEHAVIOR, kept for compatibility
|
| 165 |
+
task_id=task_id,
|
| 166 |
+
config=wrapper_config,
|
| 167 |
+
checkpoint_switcher=checkpoint_switcher
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
if checkpoint_switcher:
|
| 171 |
+
logging.info("Multi-checkpoint mode: checkpoints will switch based on task_id from observations")
|
| 172 |
+
else:
|
| 173 |
+
logging.info("Rolling inpainting enabled: will use initial_actions from input batch when provided")
|
| 174 |
+
|
| 175 |
+
hostname = socket.gethostname()
|
| 176 |
+
local_ip = socket.gethostbyname(hostname)
|
| 177 |
+
logging.info("Creating server (host: %s, ip: %s)", hostname, local_ip)
|
| 178 |
+
|
| 179 |
+
server = WebsocketPolicyServer(
|
| 180 |
+
policy=policy,
|
| 181 |
+
host="0.0.0.0",
|
| 182 |
+
port=args.port,
|
| 183 |
+
metadata=policy_metadata,
|
| 184 |
+
)
|
| 185 |
+
server.serve_forever()
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
if __name__ == "__main__":
|
| 189 |
+
logging.basicConfig(level=logging.INFO, force=True)
|
| 190 |
+
main(tyro.cli(Args))
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/scripts/train_2026.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Train / correctness-gate the 2025 PiBehavior model on the 2026 v3 subset.
|
| 2 |
+
|
| 3 |
+
Reuses scripts/train.py's main() but swaps the data loader for the v3 reader
|
| 4 |
+
(b1k.training.b1k_2026) and initializes weights from a 2025 checkpoint.
|
| 5 |
+
|
| 6 |
+
Env knobs:
|
| 7 |
+
B1K_2026_ROOT dataset root (default: checkpoint_3's 13-task subset)
|
| 8 |
+
B1K_INIT_PARAMS 2025 checkpoint params dir to init from (default: checkpoint_3)
|
| 9 |
+
B1K_ACTIVITIES comma-separated activity ids to train/evaluate (default: ck3 subset)
|
| 10 |
+
B1K_BASE_CONFIG training config to clone (default: pi_behavior_b1k_fast)
|
| 11 |
+
USE_DA3_SPATIAL Enable precomputed DA3 spatial-token adapter (default 0)
|
| 12 |
+
DA3_SPATIAL_TOKENS Number of precomputed DA3 tokens per sample (default 320)
|
| 13 |
+
DA3_SPATIAL_DIM Feature width of each DA3 token (default 1024)
|
| 14 |
+
DA3_SPATIAL_HEADS Cross-attention heads (default 8)
|
| 15 |
+
DA3_SPATIAL_SCALE Residual scale for the DA3 adapter (default 1.0)
|
| 16 |
+
BS global batch size (must be divisible by #devices; default 16)
|
| 17 |
+
FSDP_DEVICES number of devices for FSDP sharding (default: repo config)
|
| 18 |
+
NW dataloader workers (default 24)
|
| 19 |
+
STEPS num_train_steps (default 40 — a gate, not a full run)
|
| 20 |
+
FLOW num_flow_samples (default 4; paper uses 15)
|
| 21 |
+
LR_WARMUP cosine LR warmup steps (default: repo config)
|
| 22 |
+
LR_PEAK cosine peak LR (default: repo config)
|
| 23 |
+
LR_DECAY_STEPS cosine decay steps (default: repo config)
|
| 24 |
+
LR_DECAY cosine final LR (default: repo config)
|
| 25 |
+
SAVE_INTERVAL checkpoint interval (default: disabled during gates)
|
| 26 |
+
KEEP_PERIOD checkpoint keep period (default: repo config)
|
| 27 |
+
RESUME resume existing checkpoint directory (default 0)
|
| 28 |
+
OVERWRITE overwrite checkpoint directory (default 1 unless RESUME=1)
|
| 29 |
+
LOG_INTERVAL metric logging interval (default 10)
|
| 30 |
+
SKIP_IMAGE_LOG skip first-batch image logging (default 1)
|
| 31 |
+
"""
|
| 32 |
+
import logging
|
| 33 |
+
import os
|
| 34 |
+
import sys
|
| 35 |
+
import json
|
| 36 |
+
import dataclasses
|
| 37 |
+
|
| 38 |
+
# JAX-friendly + headless defaults
|
| 39 |
+
os.environ.setdefault("WANDB_MODE", "disabled")
|
| 40 |
+
os.environ.setdefault("XLA_PYTHON_CLIENT_MEM_FRACTION", "0.9")
|
| 41 |
+
os.environ.setdefault("SKIP_IMAGE_LOG", "1")
|
| 42 |
+
|
| 43 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # so `import train` (scripts/train.py) works
|
| 44 |
+
|
| 45 |
+
from b1k.training import config as _config
|
| 46 |
+
from b1k.training import data_loader as _data_loader
|
| 47 |
+
from b1k.training import weight_loaders
|
| 48 |
+
from b1k.training.b1k_2026 import create_v3_behavior_data_loader
|
| 49 |
+
from b1k.training.b1k_da3 import create_v3_behavior_da3_loader
|
| 50 |
+
|
| 51 |
+
ROOT = os.environ.get("B1K_2026_ROOT", "/work/jack/behavior1k/data/behavior_2026_ck3")
|
| 52 |
+
INIT_PARAMS = os.environ.get("B1K_INIT_PARAMS", "/work/jack/behavior1k/checkpoints/checkpoint_3/params")
|
| 53 |
+
BASE_CONFIG = os.environ.get("B1K_BASE_CONFIG", "pi_behavior_b1k_fast")
|
| 54 |
+
TASK_DATA_JSON = os.environ.get("B1K_TASK_DATA_JSON", "/work/jack/behavior1k/task_data.json")
|
| 55 |
+
if os.environ.get("B1K_ACTIVITIES"):
|
| 56 |
+
ACTIVITIES = [x.strip() for x in os.environ["B1K_ACTIVITIES"].split(",") if x.strip()]
|
| 57 |
+
else:
|
| 58 |
+
ACTIVITIES = json.load(open("/work/jack/behavior1k/subset_plan.json"))["ck3_names"]
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _v3_loader(config, *, sharding=None, shuffle=False, num_batches=None, skip_norm_stats=False):
|
| 62 |
+
if "SHUFFLE" in os.environ:
|
| 63 |
+
shuffle = bool(int(os.environ["SHUFFLE"]))
|
| 64 |
+
if getattr(config.model, "da3", None) is not None and config.model.da3.enabled:
|
| 65 |
+
return create_v3_behavior_da3_loader(
|
| 66 |
+
config, ROOT, ACTIVITIES, TASK_DATA_JSON,
|
| 67 |
+
lang_cache=os.environ.get("DA3_LANG_CACHE", "/work/jack/behavior1k/modernbert_b1k_tasks.pkl"),
|
| 68 |
+
sharding=sharding, shuffle=shuffle, num_workers=config.num_workers, seed=config.seed or 0,
|
| 69 |
+
)
|
| 70 |
+
return create_v3_behavior_data_loader(
|
| 71 |
+
config, ROOT, ACTIVITIES, TASK_DATA_JSON,
|
| 72 |
+
sharding=sharding, shuffle=shuffle, num_workers=config.num_workers, seed=config.seed or 0,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
# Swap the loader everywhere main() reaches it.
|
| 77 |
+
_data_loader.create_behavior_data_loader = _v3_loader
|
| 78 |
+
import train # scripts/train.py — defines main()
|
| 79 |
+
train._data_loader.create_behavior_data_loader = _v3_loader
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def build_config() -> _config.TrainConfig:
|
| 83 |
+
c = _config.get_config(BASE_CONFIG)
|
| 84 |
+
model = c.model
|
| 85 |
+
lr_schedule = c.lr_schedule
|
| 86 |
+
if any(k in os.environ for k in ("LR_WARMUP", "LR_PEAK", "LR_DECAY_STEPS", "LR_DECAY")):
|
| 87 |
+
lr_schedule = _config._optimizer.CosineDecaySchedule(
|
| 88 |
+
warmup_steps=int(os.environ.get("LR_WARMUP", str(lr_schedule.warmup_steps))),
|
| 89 |
+
peak_lr=float(os.environ.get("LR_PEAK", str(lr_schedule.peak_lr))),
|
| 90 |
+
decay_steps=int(os.environ.get("LR_DECAY_STEPS", str(lr_schedule.decay_steps))),
|
| 91 |
+
decay_lr=float(os.environ.get("LR_DECAY", str(lr_schedule.decay_lr))),
|
| 92 |
+
)
|
| 93 |
+
if bool(int(os.environ.get("USE_DA3_FULL", "0"))):
|
| 94 |
+
from b1k.models.pi_behavior_config import B1KDA3Config
|
| 95 |
+
model = dataclasses.replace(
|
| 96 |
+
model,
|
| 97 |
+
da3=B1KDA3Config(
|
| 98 |
+
spatial_scale=float(os.environ.get("DA3_SCALE", "2.0")),
|
| 99 |
+
spatial_init_std=float(os.environ.get("DA3_INIT_STD", "0.01")),
|
| 100 |
+
attn_logit_gain=bool(int(os.environ.get("DA3_LOGIT_GAIN", "1"))),
|
| 101 |
+
# Gains RETUNED 2026-07-22 for the qk_norm regime. With QK-norm the logits are
|
| 102 |
+
# O(1), so these act as a real temperature: measured eff-tokens-attended of 324 is
|
| 103 |
+
# gain 1 -> 202 (avg-pool), 2 -> 64, 3 -> 20, 4 -> 9, 8 -> 2.5, 16 -> 1.5 (one-hot).
|
| 104 |
+
# The old 32/8 defaults were calibrated for UNBOUNDED logits and are one-hot here.
|
| 105 |
+
attn_logit_gain_init=float(os.environ.get("DA3_LOGIT_GAIN_INIT", "3.0")),
|
| 106 |
+
attn_logit_gain_max=float(os.environ.get("DA3_INJ_GAIN_MAX", "8.0")),
|
| 107 |
+
perceiver_logit_gain=bool(int(os.environ.get("DA3_PERC_LOGIT_GAIN", "1"))),
|
| 108 |
+
perceiver_logit_gain_init=float(os.environ.get("DA3_PERC_GAIN_INIT", "3.0")),
|
| 109 |
+
perceiver_logit_gain_max=float(os.environ.get("DA3_PERC_GAIN_MAX", "8.0")),
|
| 110 |
+
perceiver_norm_attn_out=bool(int(os.environ.get("DA3_PERC_NORM_OUT", "1"))),
|
| 111 |
+
qk_norm=bool(int(os.environ.get("DA3_QK_NORM", "1"))),
|
| 112 |
+
perceiver_norm_out=bool(int(os.environ.get("DA3_PERC_NORM_FINAL", "1"))),
|
| 113 |
+
pos_emb_scale=float(os.environ.get("DA3_POS_EMB_SCALE", "0.25")),
|
| 114 |
+
bank_center=bool(int(os.environ.get("DA3_BANK_CENTER", "0"))),
|
| 115 |
+
aux_geom_head=bool(int(os.environ.get("DA3_AUX_GEOM_HEAD", "0"))),
|
| 116 |
+
aux_geom_weight=float(os.environ.get("DA3_AUX_GEOM_WEIGHT", "0.0")),
|
| 117 |
+
depth_target_only=bool(int(os.environ.get("DA3_DEPTH_TARGET_ONLY", "0"))),
|
| 118 |
+
kv_split=bool(int(os.environ.get("DA3_KV_SPLIT", "0"))),
|
| 119 |
+
depth_dropout=float(os.environ.get("DA3_DEPTH_DROPOUT", "0.0")),
|
| 120 |
+
perc_locality=bool(int(os.environ.get("DA3_PERC_LOCALITY", "0"))),
|
| 121 |
+
cross_view=bool(int(os.environ.get("DA3_CROSS_VIEW", "0"))),
|
| 122 |
+
cross_view_depth=int(os.environ.get("DA3_CROSS_VIEW_DEPTH", "2")),
|
| 123 |
+
bank_token_embed_query=bool(int(os.environ.get("DA3_BTE_QUERY", "1"))),
|
| 124 |
+
da3_channels=int(os.environ.get("DA3_CHANNELS", "1536")),
|
| 125 |
+
grid_hw=(int(os.environ.get("DA3_GRID_H", "18")), int(os.environ.get("DA3_GRID_W", "18"))),
|
| 126 |
+
use_depth_conf=bool(int(os.environ.get("DA3_USE_DEPTH_CONF", "0"))),
|
| 127 |
+
use_pose_enc=bool(int(os.environ.get("DA3_USE_POSE_ENC", "0"))),
|
| 128 |
+
use_cam_tokens=bool(int(os.environ.get("DA3_USE_CAM_TOKENS", "0"))),
|
| 129 |
+
cam_token_dim=int(os.environ.get("DA3_CAM_TOKEN_DIM", "2048")),
|
| 130 |
+
feat_input_norm=bool(int(os.environ.get("DA3_FEAT_INPUT_NORM", "0"))),
|
| 131 |
+
),
|
| 132 |
+
)
|
| 133 |
+
if bool(int(os.environ.get("USE_DA3_SPATIAL", "0"))):
|
| 134 |
+
model = dataclasses.replace(
|
| 135 |
+
model,
|
| 136 |
+
use_spatial_action_cross_attention=True,
|
| 137 |
+
spatial_num_tokens=int(os.environ.get("DA3_SPATIAL_TOKENS", "320")),
|
| 138 |
+
spatial_token_dim=int(os.environ.get("DA3_SPATIAL_DIM", "1024")),
|
| 139 |
+
spatial_num_heads=int(os.environ.get("DA3_SPATIAL_HEADS", "8")),
|
| 140 |
+
spatial_residual_scale=float(os.environ.get("DA3_SPATIAL_SCALE", "1.0")),
|
| 141 |
+
)
|
| 142 |
+
# HARD-FREEZE the base: train ONLY the spatial branch. freeze_filter matches everything that is
|
| 143 |
+
# NOT spatial, so trainable_filter = All(Param, Not(freeze)) resolves to spatial-only. Because the
|
| 144 |
+
# train step restricts BOTH the grad (nnx.DiffState) and the optimizer state (tx.init) to the
|
| 145 |
+
# trainable filter, this (a) prunes the entire base backward -- true "no backward weights" -- and
|
| 146 |
+
# (b) never allocates Adam moments for the ~3.4B base params (~30GB freed -> room for a bigger BS).
|
| 147 |
+
_extra = {}
|
| 148 |
+
if bool(int(os.environ.get("DA3_FREEZE_BASE_HARD", "0"))):
|
| 149 |
+
import flax.nnx as _nnx
|
| 150 |
+
import openpi.shared.nnx_utils as _nnxu
|
| 151 |
+
_spatial = _nnxu.PathRegex(r".*(spatial_bank_builder|spatial_inject).*")
|
| 152 |
+
_extra["freeze_filter"] = _nnx.Not(_spatial) # freeze all non-spatial params
|
| 153 |
+
logging.info("DA3 HARD FREEZE: training ONLY spatial params (base grad + base Adam state skipped)")
|
| 154 |
+
|
| 155 |
+
return dataclasses.replace(
|
| 156 |
+
c,
|
| 157 |
+
exp_name=os.environ.get("EXP", "v3_ck3_gate"),
|
| 158 |
+
model=model,
|
| 159 |
+
lr_schedule=lr_schedule,
|
| 160 |
+
weight_loader=weight_loaders.PiBehaviorWeightLoader(INIT_PARAMS),
|
| 161 |
+
wandb_enabled=False,
|
| 162 |
+
**_extra,
|
| 163 |
+
overwrite=bool(int(os.environ.get("OVERWRITE", "0" if os.environ.get("RESUME", "0") == "1" else "1"))),
|
| 164 |
+
resume=bool(int(os.environ.get("RESUME", "0"))),
|
| 165 |
+
batch_size=int(os.environ.get("BS", "16")),
|
| 166 |
+
fsdp_devices=int(os.environ.get("FSDP_DEVICES", str(c.fsdp_devices))),
|
| 167 |
+
num_workers=int(os.environ.get("NW", "24")),
|
| 168 |
+
num_train_steps=int(os.environ.get("STEPS", "40")),
|
| 169 |
+
num_flow_samples=int(os.environ.get("FLOW", "4")),
|
| 170 |
+
log_interval=int(os.environ.get("LOG_INTERVAL", "10")),
|
| 171 |
+
save_interval=int(os.environ.get("SAVE_INTERVAL", "10000000")), # disabled during gates by default
|
| 172 |
+
keep_period=int(os.environ.get("KEEP_PERIOD", str(c.keep_period))),
|
| 173 |
+
seed=0,
|
| 174 |
+
assets_base_dir="./outputs/assets",
|
| 175 |
+
checkpoint_base_dir="./outputs/checkpoints",
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
if __name__ == "__main__":
|
| 180 |
+
train.main(build_config())
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/observation.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Observation class and preprocessing with FAST auxiliary fields support.
|
| 2 |
+
|
| 3 |
+
Based on openpi with FAST fields added for PI_BEHAVIOR model.
|
| 4 |
+
|
| 5 |
+
Reference: https://github.com/wensi-ai/openpi/blob/behavior/src/openpi/models/model.py
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from collections.abc import Sequence
|
| 9 |
+
from typing import Generic, TypeVar
|
| 10 |
+
import dataclasses
|
| 11 |
+
|
| 12 |
+
import augmax
|
| 13 |
+
from flax import struct
|
| 14 |
+
import jax
|
| 15 |
+
import jax.numpy as jnp
|
| 16 |
+
import numpy as np
|
| 17 |
+
import torch
|
| 18 |
+
|
| 19 |
+
from openpi.shared import image_tools
|
| 20 |
+
from openpi.shared import array_typing as at
|
| 21 |
+
|
| 22 |
+
ArrayT = TypeVar("ArrayT", bound=jax.Array | torch.Tensor | np.ndarray)
|
| 23 |
+
|
| 24 |
+
IMAGE_KEYS = (
|
| 25 |
+
"base_0_rgb",
|
| 26 |
+
"left_wrist_0_rgb",
|
| 27 |
+
"right_wrist_0_rgb",
|
| 28 |
+
)
|
| 29 |
+
IMAGE_RESOLUTION = (224, 224)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
@at.typecheck
|
| 33 |
+
@struct.dataclass
|
| 34 |
+
class Observation(Generic[ArrayT]):
|
| 35 |
+
"""Observation with FAST auxiliary fields."""
|
| 36 |
+
|
| 37 |
+
images: dict[str, at.Float[ArrayT, "*b h w c"]]
|
| 38 |
+
image_masks: dict[str, at.Bool[ArrayT, "*b"]]
|
| 39 |
+
state: at.Float[ArrayT, "*b s"]
|
| 40 |
+
tokenized_prompt: at.Int[ArrayT, "*b l"] | None = None
|
| 41 |
+
tokenized_prompt_mask: at.Bool[ArrayT, "*b l"] | None = None
|
| 42 |
+
token_ar_mask: at.Int[ArrayT, "*b l"] | None = None
|
| 43 |
+
token_loss_mask: at.Bool[ArrayT, "*b l"] | None = None
|
| 44 |
+
|
| 45 |
+
fast_tokens: at.Int[ArrayT, "*b t"] | None = None
|
| 46 |
+
fast_token_mask: at.Bool[ArrayT, "*b t"] | None = None
|
| 47 |
+
spatial_tokens: at.Float[ArrayT, "*b n d"] | None = None
|
| 48 |
+
spatial_token_mask: at.Bool[ArrayT, "*b n"] | None = None
|
| 49 |
+
# DA3 inline spatial inputs (frozen DA3-GIANT features + geometry; consumed by the trainable
|
| 50 |
+
# bank builder in PiBehavior). da3_features ship as raw bits: uint16=bf16 bits, uint8=fp8 bytes.
|
| 51 |
+
da3_features: at.Num[ArrayT, "*b dl v dc gh gw"] | None = None
|
| 52 |
+
da3_ray: at.Float[ArrayT, "*b v three gh gw"] | None = None
|
| 53 |
+
da3_depth: at.Float[ArrayT, "*b v one gh gw"] | None = None
|
| 54 |
+
# VGGT-Omega enrichments (None on the DA3 path): per-patch depth confidence, per-view pose
|
| 55 |
+
# encoding (trans3+quat4+fov2), and camera+register global tokens.
|
| 56 |
+
da3_depth_conf: at.Float[ArrayT, "*b v one gh gw"] | None = None
|
| 57 |
+
da3_pose_enc: at.Float[ArrayT, "*b v pe"] | None = None
|
| 58 |
+
da3_cam_tokens: at.Float[ArrayT, "*b v ct cd"] | None = None
|
| 59 |
+
camera_extrinsics: at.Float[ArrayT, "*b v four four2"] | None = None
|
| 60 |
+
lang_feat: at.Float[ArrayT, "*b lt ld"] | None = None
|
| 61 |
+
lang_mask: at.Bool[ArrayT, "*b lt"] | None = None
|
| 62 |
+
|
| 63 |
+
@classmethod
|
| 64 |
+
def from_dict(cls, data: at.PyTree[ArrayT]) -> "Observation[ArrayT]":
|
| 65 |
+
"""Convert dict to Observation."""
|
| 66 |
+
if ("tokenized_prompt" in data) != ("tokenized_prompt_mask" in data):
|
| 67 |
+
raise ValueError("tokenized_prompt and tokenized_prompt_mask must be provided together.")
|
| 68 |
+
|
| 69 |
+
# Convert uint8 images to float32 [-1, 1]
|
| 70 |
+
for key in data["image"]:
|
| 71 |
+
if data["image"][key].dtype == np.uint8:
|
| 72 |
+
data["image"][key] = data["image"][key].astype(np.float32) / 255.0 * 2.0 - 1.0
|
| 73 |
+
elif hasattr(data["image"][key], "dtype") and data["image"][key].dtype == torch.uint8:
|
| 74 |
+
data["image"][key] = data["image"][key].to(torch.float32).permute(0, 3, 1, 2) / 255.0 * 2.0 - 1.0
|
| 75 |
+
|
| 76 |
+
return cls(
|
| 77 |
+
images=data["image"],
|
| 78 |
+
image_masks=data["image_mask"],
|
| 79 |
+
state=data["state"],
|
| 80 |
+
tokenized_prompt=data.get("tokenized_prompt"),
|
| 81 |
+
tokenized_prompt_mask=data.get("tokenized_prompt_mask"),
|
| 82 |
+
token_ar_mask=data.get("token_ar_mask"),
|
| 83 |
+
token_loss_mask=data.get("token_loss_mask"),
|
| 84 |
+
fast_tokens=data.get("fast_tokens"),
|
| 85 |
+
fast_token_mask=data.get("fast_token_mask"),
|
| 86 |
+
spatial_tokens=data.get("spatial_tokens"),
|
| 87 |
+
spatial_token_mask=data.get("spatial_token_mask"),
|
| 88 |
+
da3_features=data.get("da3_features"),
|
| 89 |
+
da3_ray=data.get("da3_ray"),
|
| 90 |
+
da3_depth=data.get("da3_depth"),
|
| 91 |
+
da3_depth_conf=data.get("da3_depth_conf"),
|
| 92 |
+
da3_pose_enc=data.get("da3_pose_enc"),
|
| 93 |
+
da3_cam_tokens=data.get("da3_cam_tokens"),
|
| 94 |
+
camera_extrinsics=data.get("camera_extrinsics"),
|
| 95 |
+
lang_feat=data.get("lang_feat"),
|
| 96 |
+
lang_mask=data.get("lang_mask"),
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
def to_dict(self) -> at.PyTree[ArrayT]:
|
| 100 |
+
"""Convert Observation to dict."""
|
| 101 |
+
result = dataclasses.asdict(self)
|
| 102 |
+
result["image"] = result.pop("images")
|
| 103 |
+
result["image_mask"] = result.pop("image_masks")
|
| 104 |
+
return result
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def preprocess_observation(
|
| 108 |
+
rng: at.KeyArrayLike | None,
|
| 109 |
+
observation: Observation,
|
| 110 |
+
*,
|
| 111 |
+
train: bool = False,
|
| 112 |
+
image_keys: Sequence[str] = IMAGE_KEYS,
|
| 113 |
+
image_resolution: tuple[int, int] = IMAGE_RESOLUTION,
|
| 114 |
+
) -> Observation:
|
| 115 |
+
"""Preprocess observations with image augmentation and FAST fields preservation."""
|
| 116 |
+
if not set(image_keys).issubset(observation.images):
|
| 117 |
+
raise ValueError(f"images dict missing keys: expected {image_keys}, got {list(observation.images)}")
|
| 118 |
+
|
| 119 |
+
batch_shape = observation.state.shape[:-1]
|
| 120 |
+
|
| 121 |
+
out_images = {}
|
| 122 |
+
for key in image_keys:
|
| 123 |
+
image = observation.images[key]
|
| 124 |
+
if image.shape[1:3] != image_resolution:
|
| 125 |
+
image = image_tools.resize_with_pad(image, *image_resolution)
|
| 126 |
+
|
| 127 |
+
if train:
|
| 128 |
+
# Convert from [-1, 1] to [0, 1] for augmax
|
| 129 |
+
image = image / 2.0 + 0.5
|
| 130 |
+
|
| 131 |
+
transforms = []
|
| 132 |
+
if "wrist" not in key:
|
| 133 |
+
height, width = image.shape[1:3]
|
| 134 |
+
transforms += [
|
| 135 |
+
augmax.RandomCrop(int(width * 0.95), int(height * 0.95)),
|
| 136 |
+
augmax.Resize(width, height),
|
| 137 |
+
augmax.Rotate((-5, 5)),
|
| 138 |
+
]
|
| 139 |
+
transforms += [
|
| 140 |
+
augmax.ColorJitter(brightness=0.3, contrast=0.4, saturation=0.5),
|
| 141 |
+
]
|
| 142 |
+
sub_rngs = jax.random.split(rng, image.shape[0])
|
| 143 |
+
image = jax.vmap(augmax.Chain(*transforms))(sub_rngs, image)
|
| 144 |
+
|
| 145 |
+
# Back to [-1, 1]
|
| 146 |
+
image = image * 2.0 - 1.0
|
| 147 |
+
|
| 148 |
+
out_images[key] = image
|
| 149 |
+
|
| 150 |
+
# Obtain masks
|
| 151 |
+
out_masks = {}
|
| 152 |
+
for key in out_images:
|
| 153 |
+
if key not in observation.image_masks:
|
| 154 |
+
out_masks[key] = jnp.ones(batch_shape, dtype=jnp.bool)
|
| 155 |
+
else:
|
| 156 |
+
out_masks[key] = jnp.asarray(observation.image_masks[key])
|
| 157 |
+
|
| 158 |
+
return Observation(
|
| 159 |
+
images=out_images,
|
| 160 |
+
image_masks=out_masks,
|
| 161 |
+
state=observation.state,
|
| 162 |
+
tokenized_prompt=observation.tokenized_prompt,
|
| 163 |
+
tokenized_prompt_mask=observation.tokenized_prompt_mask,
|
| 164 |
+
token_ar_mask=observation.token_ar_mask,
|
| 165 |
+
token_loss_mask=observation.token_loss_mask,
|
| 166 |
+
fast_tokens=getattr(observation, 'fast_tokens', None),
|
| 167 |
+
fast_token_mask=getattr(observation, 'fast_token_mask', None),
|
| 168 |
+
spatial_tokens=getattr(observation, 'spatial_tokens', None),
|
| 169 |
+
da3_features=getattr(observation, 'da3_features', None),
|
| 170 |
+
da3_ray=getattr(observation, 'da3_ray', None),
|
| 171 |
+
da3_depth=getattr(observation, 'da3_depth', None),
|
| 172 |
+
camera_extrinsics=getattr(observation, 'camera_extrinsics', None),
|
| 173 |
+
lang_feat=getattr(observation, 'lang_feat', None),
|
| 174 |
+
lang_mask=getattr(observation, 'lang_mask', None),
|
| 175 |
+
spatial_token_mask=getattr(observation, 'spatial_token_mask', None),
|
| 176 |
+
)
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/pi_behavior.py
ADDED
|
@@ -0,0 +1,1327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""The main model for BEHAVIOR-1K challenge.
|
| 2 |
+
|
| 3 |
+
Based on Pi0.5 implementation from PhysicalIntelligence/openpi
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
import pathlib
|
| 8 |
+
|
| 9 |
+
import einops
|
| 10 |
+
import flax.linen as nn
|
| 11 |
+
import flax.nnx as nnx
|
| 12 |
+
import flax.nnx.bridge as nnx_bridge
|
| 13 |
+
import jax
|
| 14 |
+
import jax.numpy as jnp
|
| 15 |
+
from typing_extensions import override
|
| 16 |
+
|
| 17 |
+
from openpi.models import model as _model
|
| 18 |
+
from openpi.models import gemma as _gemma
|
| 19 |
+
from b1k.models import spatial_da3 as _spatial_da3
|
| 20 |
+
from openpi.models import siglip as _siglip
|
| 21 |
+
from openpi.models.pi0 import make_attn_mask, posemb_sincos
|
| 22 |
+
from openpi.shared import array_typing as at
|
| 23 |
+
|
| 24 |
+
# Import from our custom modules
|
| 25 |
+
from b1k.models import pi_behavior_config
|
| 26 |
+
from b1k.models.observation import Observation, preprocess_observation
|
| 27 |
+
from b1k.models.pi_behavior_config import (
|
| 28 |
+
TASK_NUM_STAGES,
|
| 29 |
+
MAX_NUM_STAGES,
|
| 30 |
+
TOTAL_TASK_STAGE_EMBEDDINGS,
|
| 31 |
+
TASK_STAGE_OFFSETS
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
logger = logging.getLogger("b1k")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class KVCacheTransform(nnx.Module):
|
| 38 |
+
"""Transforms prefix KV cache by mixing across layers.
|
| 39 |
+
|
| 40 |
+
Each destination layer's K and V become learnable linear combinations
|
| 41 |
+
of all source layers' K and V, plus a bias term. This allows the action
|
| 42 |
+
expert to attend to learned combinations of VLM layers rather than being
|
| 43 |
+
forced to attend layer-by-layer.
|
| 44 |
+
|
| 45 |
+
Initialized as identity transform (k_coeffs = I, bias = 0) so the model
|
| 46 |
+
starts with the same behavior as without transformation.
|
| 47 |
+
"""
|
| 48 |
+
|
| 49 |
+
def __init__(self, num_layers: int, head_dim: int, num_kv_heads: int, rngs: nnx.Rngs):
|
| 50 |
+
# K transformation: [dest_layer, src_layer]
|
| 51 |
+
# Initialize as identity so transformation is initially a no-op
|
| 52 |
+
self.k_coeffs = nnx.Param(jnp.eye(num_layers, dtype=jnp.float32))
|
| 53 |
+
|
| 54 |
+
# K bias: [layer, num_kv_heads, head_dim]
|
| 55 |
+
# Initialize as zeros
|
| 56 |
+
self.k_bias = nnx.Param(jnp.zeros((num_layers, num_kv_heads, head_dim), dtype=jnp.float32))
|
| 57 |
+
|
| 58 |
+
# V transformation (independent from K)
|
| 59 |
+
self.v_coeffs = nnx.Param(jnp.eye(num_layers, dtype=jnp.float32))
|
| 60 |
+
self.v_bias = nnx.Param(jnp.zeros((num_layers, num_kv_heads, head_dim), dtype=jnp.float32))
|
| 61 |
+
|
| 62 |
+
def __call__(self, kv_cache: tuple[jnp.ndarray, jnp.ndarray]) -> tuple[jnp.ndarray, jnp.ndarray]:
|
| 63 |
+
"""Transform KV cache by mixing across layers.
|
| 64 |
+
|
| 65 |
+
Args:
|
| 66 |
+
kv_cache: Tuple of (cache_k, cache_v) where each has shape
|
| 67 |
+
[num_layers, batch, seq_len, num_kv_heads, head_dim]
|
| 68 |
+
|
| 69 |
+
Returns:
|
| 70 |
+
Transformed (k_new, v_new) with same shape and dtype as input
|
| 71 |
+
"""
|
| 72 |
+
cache_k, cache_v = kv_cache
|
| 73 |
+
# Shape: [layers, batch, seq_len, num_kv_heads, head_dim]
|
| 74 |
+
|
| 75 |
+
# Preserve original dtype (important for bfloat16 training)
|
| 76 |
+
original_dtype = cache_k.dtype
|
| 77 |
+
|
| 78 |
+
# Transform K: each destination layer is a weighted combination of all source layers
|
| 79 |
+
# k_new[dest] = sum_src(k_coeffs[dest, src] * cache_k[src]) + k_bias[dest]
|
| 80 |
+
# Einsum: [dest, src] @ [src, batch, seq, heads, dim] -> [dest, batch, seq, heads, dim]
|
| 81 |
+
k_new = jnp.einsum('ds,sbtkh->dbtkh', self.k_coeffs.value, cache_k)
|
| 82 |
+
k_new = k_new + self.k_bias.value[:, None, None, :, :] # Add bias
|
| 83 |
+
|
| 84 |
+
# Transform V (same operation, independent parameters)
|
| 85 |
+
v_new = jnp.einsum('ds,sbtkh->dbtkh', self.v_coeffs.value, cache_v)
|
| 86 |
+
v_new = v_new + self.v_bias.value[:, None, None, :, :]
|
| 87 |
+
|
| 88 |
+
# Cast back to original dtype
|
| 89 |
+
k_new = k_new.astype(original_dtype)
|
| 90 |
+
v_new = v_new.astype(original_dtype)
|
| 91 |
+
|
| 92 |
+
return (k_new, v_new)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class SpatialActionCrossAttention(nnx.Module):
|
| 96 |
+
"""Residual cross-attention from action tokens to precomputed DA3 spatial tokens."""
|
| 97 |
+
|
| 98 |
+
def __init__(
|
| 99 |
+
self,
|
| 100 |
+
action_width: int,
|
| 101 |
+
spatial_width: int,
|
| 102 |
+
num_heads: int,
|
| 103 |
+
rngs: nnx.Rngs,
|
| 104 |
+
):
|
| 105 |
+
if action_width % num_heads != 0:
|
| 106 |
+
raise ValueError(f"action_width={action_width} must be divisible by num_heads={num_heads}")
|
| 107 |
+
|
| 108 |
+
self.num_heads = num_heads
|
| 109 |
+
self.head_dim = action_width // num_heads
|
| 110 |
+
self.q_proj = nnx.Linear(action_width, action_width, use_bias=False, rngs=rngs)
|
| 111 |
+
self.k_proj = nnx.Linear(spatial_width, action_width, use_bias=False, rngs=rngs)
|
| 112 |
+
self.v_proj = nnx.Linear(spatial_width, action_width, use_bias=False, rngs=rngs)
|
| 113 |
+
self.out_proj = nnx.Linear(
|
| 114 |
+
action_width,
|
| 115 |
+
action_width,
|
| 116 |
+
kernel_init=nn.initializers.zeros,
|
| 117 |
+
bias_init=nn.initializers.zeros,
|
| 118 |
+
rngs=rngs,
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
def __call__(
|
| 122 |
+
self,
|
| 123 |
+
action_tokens: jnp.ndarray,
|
| 124 |
+
spatial_tokens: jnp.ndarray,
|
| 125 |
+
spatial_token_mask: jnp.ndarray | None = None,
|
| 126 |
+
*,
|
| 127 |
+
residual_scale: float = 1.0,
|
| 128 |
+
) -> jnp.ndarray:
|
| 129 |
+
original_dtype = action_tokens.dtype
|
| 130 |
+
q = self.q_proj(action_tokens)
|
| 131 |
+
k = self.k_proj(spatial_tokens.astype(action_tokens.dtype))
|
| 132 |
+
v = self.v_proj(spatial_tokens.astype(action_tokens.dtype))
|
| 133 |
+
|
| 134 |
+
q = einops.rearrange(q, "b t (h d) -> b h t d", h=self.num_heads)
|
| 135 |
+
k = einops.rearrange(k, "b s (h d) -> b h s d", h=self.num_heads)
|
| 136 |
+
v = einops.rearrange(v, "b s (h d) -> b h s d", h=self.num_heads)
|
| 137 |
+
|
| 138 |
+
logits = jnp.einsum("bhtd,bhsd->bhts", q, k, preferred_element_type=jnp.float32)
|
| 139 |
+
logits = logits * (self.head_dim ** -0.5)
|
| 140 |
+
|
| 141 |
+
if spatial_token_mask is not None:
|
| 142 |
+
big_neg = -2.3819763e38
|
| 143 |
+
logits = jnp.where(spatial_token_mask[:, None, None, :], logits, big_neg)
|
| 144 |
+
|
| 145 |
+
probs = jax.nn.softmax(logits, axis=-1).astype(original_dtype)
|
| 146 |
+
context = jnp.einsum("bhts,bhsd->bhtd", probs, v)
|
| 147 |
+
context = einops.rearrange(context, "b h t d -> b t (h d)")
|
| 148 |
+
delta = self.out_proj(context).astype(original_dtype)
|
| 149 |
+
return action_tokens + residual_scale * delta
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class PiBehavior(_model.BaseModel):
|
| 153 |
+
def __init__(self, config: pi_behavior_config.PiBehaviorConfig, rngs: nnx.Rngs):
|
| 154 |
+
super().__init__(config.action_dim, config.action_horizon, config.max_token_len)
|
| 155 |
+
|
| 156 |
+
# Store config for later use
|
| 157 |
+
self.config = config
|
| 158 |
+
|
| 159 |
+
paligemma_config = _gemma.get_config(config.paligemma_variant)
|
| 160 |
+
action_expert_config = _gemma.get_config(config.action_expert_variant)
|
| 161 |
+
|
| 162 |
+
# Initialize Gemma models with AdaRMS (Pi05 style)
|
| 163 |
+
spatial_inject = getattr(config, "da3", None) is not None and config.da3.enabled
|
| 164 |
+
self.da3_cfg = getattr(config, "da3", None)
|
| 165 |
+
llm = nnx_bridge.ToNNX(
|
| 166 |
+
_gemma.Module(
|
| 167 |
+
configs=[paligemma_config, action_expert_config],
|
| 168 |
+
embed_dtype=config.dtype,
|
| 169 |
+
adarms=True,
|
| 170 |
+
spatial_inject=spatial_inject,
|
| 171 |
+
num_spatial_layers=config.da3.num_inject_layers if spatial_inject else 6,
|
| 172 |
+
spatial_scale=config.da3.spatial_scale if spatial_inject else 2.0,
|
| 173 |
+
spatial_init_std=config.da3.spatial_init_std if spatial_inject else 0.0,
|
| 174 |
+
spatial_logit_gain=config.da3.attn_logit_gain if spatial_inject else False,
|
| 175 |
+
spatial_logit_gain_init=config.da3.attn_logit_gain_init if spatial_inject else 1.0,
|
| 176 |
+
spatial_logit_gain_max=config.da3.attn_logit_gain_max if spatial_inject else 0.0,
|
| 177 |
+
spatial_qk_norm=config.da3.qk_norm if spatial_inject else False,
|
| 178 |
+
)
|
| 179 |
+
)
|
| 180 |
+
llm.lazy_init(rngs=rngs, method="init", use_adarms=[False, True])
|
| 181 |
+
|
| 182 |
+
# Initialize vision model
|
| 183 |
+
img = nnx_bridge.ToNNX(
|
| 184 |
+
_siglip.Module(
|
| 185 |
+
num_classes=paligemma_config.width,
|
| 186 |
+
variant="So400m/14",
|
| 187 |
+
pool_type="none",
|
| 188 |
+
scan=True,
|
| 189 |
+
dtype_mm=config.dtype,
|
| 190 |
+
)
|
| 191 |
+
)
|
| 192 |
+
img.lazy_init(next(iter(config.fake_obs().images.values())), train=False, rngs=rngs)
|
| 193 |
+
|
| 194 |
+
self.PaliGemma = nnx.Dict(llm=llm, img=img)
|
| 195 |
+
|
| 196 |
+
# DA3 spatial-language bank builder (trainable; frozen DA3 runs inline in the data pipeline).
|
| 197 |
+
self.spatial_bank_builder = None
|
| 198 |
+
if spatial_inject:
|
| 199 |
+
d = config.da3
|
| 200 |
+
self.spatial_bank_builder = _spatial_da3.SpatialBankBuilder(
|
| 201 |
+
hidden_dim=d.hidden_dim,
|
| 202 |
+
da3_channels=d.da3_channels,
|
| 203 |
+
num_layers=d.da3_layers,
|
| 204 |
+
grid_hw=d.grid_hw,
|
| 205 |
+
lang_dim=d.lang_dim,
|
| 206 |
+
num_heads=d.num_heads,
|
| 207 |
+
lang_fusion_depth=d.lang_fusion_depth,
|
| 208 |
+
perceiver_query_std=d.perceiver_query_std,
|
| 209 |
+
qk_norm=d.qk_norm,
|
| 210 |
+
perceiver_norm_out=d.perceiver_norm_out,
|
| 211 |
+
pos_emb_scale=d.pos_emb_scale,
|
| 212 |
+
perceiver_logit_gain=d.perceiver_logit_gain,
|
| 213 |
+
perceiver_logit_gain_init=d.perceiver_logit_gain_init,
|
| 214 |
+
perceiver_logit_gain_max=d.perceiver_logit_gain_max,
|
| 215 |
+
perceiver_norm_attn_out=d.perceiver_norm_attn_out,
|
| 216 |
+
bank_token_embed=d.bank_token_embed,
|
| 217 |
+
bank_center=d.bank_center,
|
| 218 |
+
aux_geom_head=d.aux_geom_head,
|
| 219 |
+
depth_target_only=d.depth_target_only,
|
| 220 |
+
kv_split=d.kv_split,
|
| 221 |
+
depth_dropout=d.depth_dropout,
|
| 222 |
+
perc_locality=d.perc_locality,
|
| 223 |
+
cross_view=d.cross_view,
|
| 224 |
+
cross_view_depth=d.cross_view_depth,
|
| 225 |
+
bank_token_embed_query=d.bank_token_embed_query,
|
| 226 |
+
use_depth_conf=d.use_depth_conf,
|
| 227 |
+
use_pose_enc=d.use_pose_enc,
|
| 228 |
+
use_cam_tokens=d.use_cam_tokens,
|
| 229 |
+
cam_token_dim=d.cam_token_dim,
|
| 230 |
+
pose_enc_dim=d.pose_enc_dim,
|
| 231 |
+
feat_input_norm=d.feat_input_norm,
|
| 232 |
+
rngs=rngs,
|
| 233 |
+
)
|
| 234 |
+
|
| 235 |
+
# KV cache transformation for cross-layer attention
|
| 236 |
+
# Allows action expert to attend to learned combinations of VLM layers
|
| 237 |
+
if config.use_kv_transform:
|
| 238 |
+
self.kv_transform = KVCacheTransform(
|
| 239 |
+
num_layers=paligemma_config.depth,
|
| 240 |
+
head_dim=paligemma_config.head_dim,
|
| 241 |
+
num_kv_heads=paligemma_config.num_kv_heads,
|
| 242 |
+
rngs=rngs
|
| 243 |
+
)
|
| 244 |
+
else:
|
| 245 |
+
self.kv_transform = None
|
| 246 |
+
|
| 247 |
+
# Task embeddings table - trainable embeddings for each task
|
| 248 |
+
self.task_embeddings = nnx.Embed(
|
| 249 |
+
num_embeddings=config.num_tasks,
|
| 250 |
+
features=config.task_embedding_dim,
|
| 251 |
+
rngs=rngs,
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
# Stage predictor - predicts stage from VLM output of base task token
|
| 255 |
+
# Outputs MAX_NUM_STAGES logits, but invalid stages are masked per task
|
| 256 |
+
self.stage_pred_from_vlm = nnx.Linear(paligemma_config.width, MAX_NUM_STAGES, rngs=rngs)
|
| 257 |
+
|
| 258 |
+
# Task + subtask fusion layers
|
| 259 |
+
# Combines task embedding + cos/sin encoded subtask state
|
| 260 |
+
self.subtask_encoding_dim = config.task_embedding_dim // 2 # Half of task embedding dim (1024)
|
| 261 |
+
|
| 262 |
+
# Task-specific stage embeddings (one per stage per task)
|
| 263 |
+
# Total embeddings = sum of stages across all tasks (596 for 5-15 stages per task)
|
| 264 |
+
self.task_stage_embeddings = nnx.Embed(
|
| 265 |
+
num_embeddings=TOTAL_TASK_STAGE_EMBEDDINGS,
|
| 266 |
+
features=self.subtask_encoding_dim,
|
| 267 |
+
rngs=rngs,
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
# Gated fusion layers
|
| 271 |
+
# Input: task_embedding + sincos + task_stage_emb = task_dim + 2*subtask_dim
|
| 272 |
+
fusion_input_dim = config.task_embedding_dim + 2 * self.subtask_encoding_dim
|
| 273 |
+
|
| 274 |
+
# Gate networks to learn how to combine different signals
|
| 275 |
+
self.gate_sincos = nnx.Linear(fusion_input_dim, self.subtask_encoding_dim, rngs=rngs)
|
| 276 |
+
self.gate_task_stage = nnx.Linear(fusion_input_dim, self.subtask_encoding_dim, rngs=rngs)
|
| 277 |
+
self.gate_task = nnx.Linear(fusion_input_dim, config.task_embedding_dim, rngs=rngs)
|
| 278 |
+
|
| 279 |
+
# Fusion networks to create multiple conditioned vectors
|
| 280 |
+
self.fusion_layer1 = nnx.Linear(fusion_input_dim, config.task_embedding_dim * 2, rngs=rngs)
|
| 281 |
+
self.fusion_layer2 = nnx.Linear(config.task_embedding_dim * 2, config.task_embedding_dim, rngs=rngs)
|
| 282 |
+
|
| 283 |
+
# Additional projection for stage-dominant representation (2 signals now)
|
| 284 |
+
self.stage_projection = nnx.Linear(2 * self.subtask_encoding_dim, config.task_embedding_dim, rngs=rngs)
|
| 285 |
+
|
| 286 |
+
# Pi05 style layers
|
| 287 |
+
self.action_in_proj = nnx.Linear(config.action_dim, action_expert_config.width, rngs=rngs)
|
| 288 |
+
self.time_mlp_in = nnx.Linear(action_expert_config.width, action_expert_config.width, rngs=rngs)
|
| 289 |
+
self.time_mlp_out = nnx.Linear(action_expert_config.width, action_expert_config.width, rngs=rngs)
|
| 290 |
+
self.action_out_proj = nnx.Linear(action_expert_config.width, config.action_dim, rngs=rngs)
|
| 291 |
+
if config.use_spatial_action_cross_attention:
|
| 292 |
+
self.spatial_action_xattn = SpatialActionCrossAttention(
|
| 293 |
+
action_width=action_expert_config.width,
|
| 294 |
+
spatial_width=config.spatial_token_dim,
|
| 295 |
+
num_heads=config.spatial_num_heads,
|
| 296 |
+
rngs=rngs,
|
| 297 |
+
)
|
| 298 |
+
logger.info(
|
| 299 |
+
"DA3 spatial action cross-attention enabled: tokens=%s dim=%s heads=%s scale=%s",
|
| 300 |
+
config.spatial_num_tokens,
|
| 301 |
+
config.spatial_token_dim,
|
| 302 |
+
config.spatial_num_heads,
|
| 303 |
+
config.spatial_residual_scale,
|
| 304 |
+
)
|
| 305 |
+
else:
|
| 306 |
+
self.spatial_action_xattn = None
|
| 307 |
+
|
| 308 |
+
# Correlated noise generation
|
| 309 |
+
# Initialize as NNX Intermediate (excluded from checkpoints, loaded from norm_stats)
|
| 310 |
+
# Full correlation matrix with beta shrinkage for robustness
|
| 311 |
+
flat_dim = config.action_horizon * config.action_dim
|
| 312 |
+
self.action_correlation_cholesky = nnx.Intermediate(
|
| 313 |
+
jnp.eye(flat_dim), # Identity matrix as placeholder
|
| 314 |
+
)
|
| 315 |
+
self.correlation_loaded = False # Track if correlation matrix has been loaded
|
| 316 |
+
self.use_correlated_noise = config.use_correlated_noise
|
| 317 |
+
self.correlation_beta = config.correlation_beta # Shrinkage parameter for regularization
|
| 318 |
+
|
| 319 |
+
# Inpainting cache: stores precomputed matrices for simple correlation-based inpainting
|
| 320 |
+
# Key: num_inpainted_steps (length of inpainted sequence)
|
| 321 |
+
# Value: dict with {O_indices, U_indices, Sigma_UO_SOOinv}
|
| 322 |
+
self.inpainting_cache = {}
|
| 323 |
+
|
| 324 |
+
# FAST auxiliary training components
|
| 325 |
+
if config.use_fast_auxiliary:
|
| 326 |
+
# FAST embedding layer (vocab_size → paligemma_width)
|
| 327 |
+
# Use paligemma width (2048) to match other prefix tokens
|
| 328 |
+
self.fast_token_embedding = nnx.Embed(
|
| 329 |
+
num_embeddings=config.fast_vocab_size,
|
| 330 |
+
features=paligemma_config.width,
|
| 331 |
+
rngs=rngs
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
# FAST projection head (paligemma_width → vocab_size)
|
| 335 |
+
self.fast_token_proj = nnx.Linear(
|
| 336 |
+
paligemma_config.width,
|
| 337 |
+
config.fast_vocab_size,
|
| 338 |
+
rngs=rngs
|
| 339 |
+
)
|
| 340 |
+
|
| 341 |
+
logger.info(f"FAST auxiliary enabled, vocab_size={config.fast_vocab_size}")
|
| 342 |
+
|
| 343 |
+
# This attribute gets automatically set by model.train() and model.eval().
|
| 344 |
+
self.deterministic = True
|
| 345 |
+
|
| 346 |
+
def _compute_banks(self, observation, return_aux=False, depth_drop_rng=None):
|
| 347 |
+
"""Build the per-view DA3 spatial banks once per forward (timestep-independent; reused
|
| 348 |
+
across all flow samples / denoise steps). If return_aux, also return the aux geometry loss.
|
| 349 |
+
depth_drop_rng enables depth_dropout (training only); None = inference, depth kept."""
|
| 350 |
+
if self.spatial_bank_builder is None or getattr(observation, "da3_features", None) is None:
|
| 351 |
+
return (None, None) if return_aux else None
|
| 352 |
+
feats = observation.da3_features
|
| 353 |
+
# Features arrive as raw BITS to minimize host<->device transfer; decode on-device to bf16.
|
| 354 |
+
# uint16 = bf16 bits (inline extractor); uint8 = fp8-e4m3fn bytes (legacy cache).
|
| 355 |
+
if feats.dtype == jnp.uint8:
|
| 356 |
+
feats = jax.lax.bitcast_convert_type(feats, jnp.float8_e4m3fn).astype(jnp.bfloat16)
|
| 357 |
+
elif feats.dtype == jnp.uint16:
|
| 358 |
+
feats = jax.lax.bitcast_convert_type(feats, jnp.bfloat16)
|
| 359 |
+
else:
|
| 360 |
+
feats = feats.astype(jnp.bfloat16)
|
| 361 |
+
return self.spatial_bank_builder(
|
| 362 |
+
feats,
|
| 363 |
+
observation.da3_ray,
|
| 364 |
+
observation.da3_depth,
|
| 365 |
+
observation.camera_extrinsics,
|
| 366 |
+
observation.lang_feat,
|
| 367 |
+
observation.lang_mask,
|
| 368 |
+
return_aux=return_aux,
|
| 369 |
+
depth_drop_rng=depth_drop_rng,
|
| 370 |
+
depth_conf=getattr(observation, "da3_depth_conf", None),
|
| 371 |
+
pose_enc=getattr(observation, "da3_pose_enc", None),
|
| 372 |
+
cam_tokens=getattr(observation, "da3_cam_tokens", None),
|
| 373 |
+
)
|
| 374 |
+
|
| 375 |
+
def apply_spatial_action_conditioning(self, observation: Observation, action_tokens: jnp.ndarray) -> jnp.ndarray:
|
| 376 |
+
"""Inject precomputed DA3 spatial tokens into action-token hidden states."""
|
| 377 |
+
if self.spatial_action_xattn is None or observation.spatial_tokens is None:
|
| 378 |
+
return action_tokens
|
| 379 |
+
|
| 380 |
+
return self.spatial_action_xattn(
|
| 381 |
+
action_tokens,
|
| 382 |
+
observation.spatial_tokens,
|
| 383 |
+
observation.spatial_token_mask,
|
| 384 |
+
residual_scale=self.config.spatial_residual_scale,
|
| 385 |
+
)
|
| 386 |
+
|
| 387 |
+
def encode_subtask_state(
|
| 388 |
+
self,
|
| 389 |
+
subtask_state: at.Int[at.Array, " b"],
|
| 390 |
+
task_ids: at.Int[at.Array, " b"]
|
| 391 |
+
) -> at.Float[at.Array, "b {self.subtask_encoding_dim}"]:
|
| 392 |
+
"""Encode subtask state using cos/sin positional encoding, scaled per task.
|
| 393 |
+
|
| 394 |
+
Args:
|
| 395 |
+
subtask_state: Current stage for each sample [B]
|
| 396 |
+
task_ids: Task ID for each sample [B]
|
| 397 |
+
|
| 398 |
+
Returns:
|
| 399 |
+
Positional encodings scaled to [0, 1] range based on task-specific stage count [B, 1024]
|
| 400 |
+
"""
|
| 401 |
+
# Get number of stages for each task in batch using JAX array indexing
|
| 402 |
+
# Convert tuple to JAX array inside function to avoid import-time device allocation
|
| 403 |
+
task_num_stages_array = jnp.array(TASK_NUM_STAGES, dtype=jnp.int32)
|
| 404 |
+
task_num_stages = task_num_stages_array[task_ids] # [B] - JAX array indexing
|
| 405 |
+
|
| 406 |
+
# Normalize: stage 0 → 0.0, last stage → 1.0 (per-task scaling)
|
| 407 |
+
# Add maximum to avoid division by zero for edge cases
|
| 408 |
+
normalized_state = subtask_state.astype(jnp.float32) / jnp.maximum(task_num_stages.astype(jnp.float32) - 1.0, 1.0)
|
| 409 |
+
|
| 410 |
+
# Use cos/sin encoding similar to timestep encoding
|
| 411 |
+
return posemb_sincos(
|
| 412 |
+
normalized_state,
|
| 413 |
+
self.subtask_encoding_dim,
|
| 414 |
+
min_period=1e-3,
|
| 415 |
+
max_period=1.0
|
| 416 |
+
)
|
| 417 |
+
|
| 418 |
+
def load_correlation_matrix(self, norm_stats: dict):
|
| 419 |
+
"""Load full correlation matrix from normalization statistics and apply shrinkage.
|
| 420 |
+
|
| 421 |
+
This should be called after model initialization when norm_stats are available.
|
| 422 |
+
Applies shrinkage regularization: S_reg = beta * S + (1-beta) * I for robustness.
|
| 423 |
+
|
| 424 |
+
Args:
|
| 425 |
+
norm_stats: Dictionary containing normalization statistics (from normalize.load()),
|
| 426 |
+
with 'actions' key containing NormStats with action_correlation_cholesky field.
|
| 427 |
+
|
| 428 |
+
Raises:
|
| 429 |
+
ValueError: If use_correlated_noise=True but correlation matrix is missing.
|
| 430 |
+
TypeError: If norm_stats structure is incorrect.
|
| 431 |
+
"""
|
| 432 |
+
if not self.use_correlated_noise:
|
| 433 |
+
logger.info("Correlated noise disabled in config, skipping correlation matrix loading")
|
| 434 |
+
return
|
| 435 |
+
|
| 436 |
+
# Validate norm_stats is a dict
|
| 437 |
+
if not isinstance(norm_stats, dict):
|
| 438 |
+
raise TypeError(
|
| 439 |
+
f"norm_stats must be a dict, got {type(norm_stats).__name__}. "
|
| 440 |
+
"Ensure norm_stats are loaded using openpi.shared.normalize.load()."
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
# Check 'actions' key exists
|
| 444 |
+
if 'actions' not in norm_stats:
|
| 445 |
+
raise ValueError(
|
| 446 |
+
"use_correlated_noise=True but 'actions' key not found in norm_stats. "
|
| 447 |
+
f"Found keys: {list(norm_stats.keys())}. "
|
| 448 |
+
"Run compute_norm_stats.py with --correlation flag to generate correlation matrix."
|
| 449 |
+
)
|
| 450 |
+
|
| 451 |
+
actions_stats = norm_stats['actions']
|
| 452 |
+
|
| 453 |
+
# Extract correlation matrix (support both dict and attribute access for flexibility)
|
| 454 |
+
if isinstance(actions_stats, dict):
|
| 455 |
+
chol_matrix = actions_stats.get('action_correlation_cholesky')
|
| 456 |
+
access_method = "dict"
|
| 457 |
+
elif hasattr(actions_stats, 'action_correlation_cholesky'):
|
| 458 |
+
chol_matrix = actions_stats.action_correlation_cholesky
|
| 459 |
+
access_method = "attribute"
|
| 460 |
+
else:
|
| 461 |
+
raise TypeError(
|
| 462 |
+
f"norm_stats['actions'] has unexpected type {type(actions_stats).__name__} "
|
| 463 |
+
f"and cannot access 'action_correlation_cholesky'. "
|
| 464 |
+
"Ensure norm_stats are loaded using openpi.shared.normalize.load()."
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
# Strict validation: correlation matrix must exist and be non-None
|
| 468 |
+
if chol_matrix is None:
|
| 469 |
+
raise ValueError(
|
| 470 |
+
"use_correlated_noise=True but 'action_correlation_cholesky' is None in norm_stats['actions']. "
|
| 471 |
+
"This means the correlation matrix was not computed during norm_stats generation. "
|
| 472 |
+
"Run compute_norm_stats.py with --correlation flag to generate correlation matrix."
|
| 473 |
+
)
|
| 474 |
+
|
| 475 |
+
logger.info(f"Successfully accessed correlation matrix via {access_method} access")
|
| 476 |
+
|
| 477 |
+
# Validate correlation matrix shape
|
| 478 |
+
expected_dim = self.action_horizon * self.action_dim
|
| 479 |
+
try:
|
| 480 |
+
L = jnp.array(chol_matrix)
|
| 481 |
+
except Exception as e:
|
| 482 |
+
raise ValueError(
|
| 483 |
+
f"Failed to convert action_correlation_cholesky to array: {e}. "
|
| 484 |
+
"The correlation matrix may be corrupted or in an invalid format."
|
| 485 |
+
)
|
| 486 |
+
|
| 487 |
+
if L.ndim != 2 or L.shape[0] != L.shape[1]:
|
| 488 |
+
raise ValueError(
|
| 489 |
+
f"action_correlation_cholesky must be a square 2D matrix, got shape {L.shape}. "
|
| 490 |
+
f"Expected shape: ({expected_dim}, {expected_dim})"
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
if L.shape[0] != expected_dim:
|
| 494 |
+
raise ValueError(
|
| 495 |
+
f"action_correlation_cholesky has wrong dimensions: {L.shape[0]}x{L.shape[0]}. "
|
| 496 |
+
f"Expected {expected_dim}x{expected_dim} (action_horizon={self.action_horizon} * action_dim={self.action_dim}). "
|
| 497 |
+
"This indicates the correlation matrix was computed for a different action space configuration."
|
| 498 |
+
)
|
| 499 |
+
|
| 500 |
+
# Reconstruct covariance matrix from Cholesky
|
| 501 |
+
Sigma = L @ L.T
|
| 502 |
+
|
| 503 |
+
# Apply shrinkage regularization: Σ_reg = beta * Σ + (1-beta) * I
|
| 504 |
+
beta = self.correlation_beta
|
| 505 |
+
logger.info(f"Applying shrinkage regularization with beta={beta:.2f}")
|
| 506 |
+
|
| 507 |
+
Sigma_reg = beta * Sigma + (1 - beta) * jnp.eye(Sigma.shape[0])
|
| 508 |
+
|
| 509 |
+
# Compute Cholesky decomposition of regularized covariance
|
| 510 |
+
try:
|
| 511 |
+
L_reg = jnp.linalg.cholesky(Sigma_reg)
|
| 512 |
+
except Exception as e:
|
| 513 |
+
raise RuntimeError(
|
| 514 |
+
f"Cholesky decomposition failed on regularized covariance: {e}. "
|
| 515 |
+
"This indicates the regularized correlation matrix is not positive definite. "
|
| 516 |
+
f"Current beta={beta:.2f}. Try decreasing correlation_beta closer to 0.0 for more shrinkage/regularization."
|
| 517 |
+
)
|
| 518 |
+
|
| 519 |
+
# Update the Intermediate value
|
| 520 |
+
self.action_correlation_cholesky.value = L_reg
|
| 521 |
+
self.correlation_loaded = True
|
| 522 |
+
|
| 523 |
+
logger.info(
|
| 524 |
+
f"✓ Loaded correlation matrix with shape {L_reg.shape} "
|
| 525 |
+
f"(beta={beta:.2f} shrinkage applied)"
|
| 526 |
+
)
|
| 527 |
+
logger.info(
|
| 528 |
+
f" Memory usage: {L_reg.nbytes / 1024 / 1024:.2f} MB"
|
| 529 |
+
)
|
| 530 |
+
|
| 531 |
+
def generate_correlated_noise(
|
| 532 |
+
self,
|
| 533 |
+
rng: at.KeyArrayLike,
|
| 534 |
+
batch_size: int,
|
| 535 |
+
) -> at.Float[at.Array, "b {self.action_horizon} {self.action_dim}"]:
|
| 536 |
+
"""Generate correlated noise matching action covariance structure.
|
| 537 |
+
|
| 538 |
+
Uses full correlation matrix with optional beta shrinkage for robustness.
|
| 539 |
+
|
| 540 |
+
Args:
|
| 541 |
+
rng: Random key for noise generation
|
| 542 |
+
batch_size: Number of noise samples to generate
|
| 543 |
+
|
| 544 |
+
Returns:
|
| 545 |
+
Correlated noise with shape [batch_size, action_horizon, action_dim]
|
| 546 |
+
|
| 547 |
+
Raises:
|
| 548 |
+
RuntimeError: If use_correlated_noise=True but correlation matrix not loaded.
|
| 549 |
+
"""
|
| 550 |
+
if not self.use_correlated_noise:
|
| 551 |
+
# Independent Gaussian noise when correlated noise is disabled
|
| 552 |
+
return jax.random.normal(rng, (batch_size, self.action_horizon, self.action_dim))
|
| 553 |
+
|
| 554 |
+
if not self.correlation_loaded:
|
| 555 |
+
raise RuntimeError(
|
| 556 |
+
"use_correlated_noise=True but correlation matrix is not loaded. "
|
| 557 |
+
"Ensure load_correlation_matrix() was called during model initialization. "
|
| 558 |
+
"Run compute_norm_stats.py with --correlation flag to generate correlation matrix."
|
| 559 |
+
)
|
| 560 |
+
|
| 561 |
+
# Generate standard correlated noise using Cholesky decomposition
|
| 562 |
+
flat_dim = self.action_horizon * self.action_dim
|
| 563 |
+
standard_normal = jax.random.normal(rng, (batch_size, flat_dim))
|
| 564 |
+
correlated_flat = standard_normal @ self.action_correlation_cholesky.value.T
|
| 565 |
+
correlated_noise = correlated_flat.reshape(batch_size, self.action_horizon, self.action_dim)
|
| 566 |
+
return correlated_noise
|
| 567 |
+
|
| 568 |
+
def _precompute_correction_matrix(
|
| 569 |
+
self,
|
| 570 |
+
O_indices: at.Int[at.Array, " nO"],
|
| 571 |
+
U_indices: at.Int[at.Array, " nU"],
|
| 572 |
+
) -> dict:
|
| 573 |
+
"""Precompute matrix for correlation-aware inpainting correction.
|
| 574 |
+
|
| 575 |
+
Computes Σ_{UO}Σ_{OO}^{-1} which propagates corrections from O to U
|
| 576 |
+
while preserving correlation structure.
|
| 577 |
+
|
| 578 |
+
Args:
|
| 579 |
+
O_indices: Flat indices of inpainted dimensions [|O|]
|
| 580 |
+
U_indices: Flat indices of free dimensions [|U|]
|
| 581 |
+
|
| 582 |
+
Returns:
|
| 583 |
+
Dictionary with {O_indices, U_indices, correction_matrix}
|
| 584 |
+
|
| 585 |
+
Raises:
|
| 586 |
+
RuntimeError: If correlation matrix is not loaded
|
| 587 |
+
"""
|
| 588 |
+
if not self.correlation_loaded:
|
| 589 |
+
raise RuntimeError(
|
| 590 |
+
"Cannot precompute correction matrix: correlation matrix not loaded. "
|
| 591 |
+
"Call load_correlation_matrix() first."
|
| 592 |
+
)
|
| 593 |
+
|
| 594 |
+
L = self.action_correlation_cholesky.value
|
| 595 |
+
Sigma = L @ L.T # Full covariance matrix [hd, hd]
|
| 596 |
+
|
| 597 |
+
# Extract submatrices
|
| 598 |
+
Sigma_OO = Sigma[jnp.ix_(O_indices, O_indices)] # [|O|, |O|]
|
| 599 |
+
Sigma_UO = Sigma[jnp.ix_(U_indices, O_indices)] # [|U|, |O|]
|
| 600 |
+
|
| 601 |
+
# Compute correction matrix: Σ_{UO} @ Σ_{OO}^{-1}
|
| 602 |
+
# This propagates corrections from O to U
|
| 603 |
+
eps_OO = 1e-6 * jnp.maximum(jnp.mean(jnp.diag(Sigma_OO)), 1.0)
|
| 604 |
+
Sigma_OO_reg = Sigma_OO + eps_OO * jnp.eye(Sigma_OO.shape[0])
|
| 605 |
+
|
| 606 |
+
# Solve Σ_{OO}_reg @ X = Σ_{UO}.T for X, then transpose
|
| 607 |
+
correction_matrix = jax.scipy.linalg.solve(
|
| 608 |
+
Sigma_OO_reg, Sigma_UO.T, assume_a='pos'
|
| 609 |
+
).T # [|U|, |O|]
|
| 610 |
+
|
| 611 |
+
return {
|
| 612 |
+
'O_indices': O_indices,
|
| 613 |
+
'U_indices': U_indices,
|
| 614 |
+
'correction_matrix': correction_matrix, # Σ_{UO}Σ_{OO}^{-1}
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
def fuse_task_and_subtask(
|
| 618 |
+
self, task_embedding: at.Float[at.Array, "b d"], task_ids: at.Int[at.Array, " b"], subtask_state: at.Int[at.Array, " b"]
|
| 619 |
+
) -> at.Float[at.Array, "b n d"]:
|
| 620 |
+
"""Fuse task embedding with subtask state encoding using multiple representations.
|
| 621 |
+
|
| 622 |
+
Returns multiple vectors that are differently conditioned by the subtask state:
|
| 623 |
+
1. Task-gated representation (task embedding modulated by subtask)
|
| 624 |
+
2. Balanced fusion (task + subtask combined)
|
| 625 |
+
3. Stage-dominant representation (subtask features projected to task space)
|
| 626 |
+
4. Pure stage representation (concatenated learned embeddings)
|
| 627 |
+
|
| 628 |
+
All output representations have dimension 2048 (task_embedding_dim).
|
| 629 |
+
|
| 630 |
+
Args:
|
| 631 |
+
task_embedding: Base task embedding [b, 2048]
|
| 632 |
+
task_ids: Task IDs for task-specific stage embeddings [b]
|
| 633 |
+
subtask_state: Subtask state indices [b]
|
| 634 |
+
|
| 635 |
+
Returns:
|
| 636 |
+
Multiple fused embeddings [b, 4, 2048]
|
| 637 |
+
"""
|
| 638 |
+
# Get subtask representations
|
| 639 |
+
sincos_encoding = self.encode_subtask_state(subtask_state, task_ids) # [b, 1024]
|
| 640 |
+
|
| 641 |
+
# Task-specific stage embedding with corrected indexing
|
| 642 |
+
# Use vectorized lookup: offset + stage for each task
|
| 643 |
+
# Convert tuple to JAX array inside function to avoid import-time device allocation
|
| 644 |
+
task_stage_offsets_array = jnp.array(TASK_STAGE_OFFSETS, dtype=jnp.int32)
|
| 645 |
+
task_stage_offsets = task_stage_offsets_array[task_ids] # [b] - JAX array indexing
|
| 646 |
+
task_stage_idx = task_stage_offsets + subtask_state # [b]
|
| 647 |
+
task_stage_embedding = self.task_stage_embeddings(task_stage_idx) # [b, 1024]
|
| 648 |
+
|
| 649 |
+
# Concatenate inputs for gating: task (2048) + sincos (1024) + task_stage (1024) = 4096
|
| 650 |
+
all_inputs = jnp.concatenate([
|
| 651 |
+
task_embedding, # [b, 2048]
|
| 652 |
+
sincos_encoding, # [b, 1024]
|
| 653 |
+
task_stage_embedding # [b, 1024]
|
| 654 |
+
], axis=-1) # [b, 4096]
|
| 655 |
+
|
| 656 |
+
# Learn gates for each component (sigmoid to get 0-1 scaling)
|
| 657 |
+
gate_sincos = nnx.sigmoid(self.gate_sincos(all_inputs)) # [b, 1024]
|
| 658 |
+
gate_task_stage = nnx.sigmoid(self.gate_task_stage(all_inputs)) # [b, 1024]
|
| 659 |
+
gate_task = nnx.sigmoid(self.gate_task(all_inputs)) # [b, 2048]
|
| 660 |
+
|
| 661 |
+
# 1. Task-gated representation: task embedding modulated by subtask info [b, 2048]
|
| 662 |
+
task_gated = task_embedding * gate_task
|
| 663 |
+
|
| 664 |
+
# 2. Balanced fusion: combine all signals through fusion network [b, 2048]
|
| 665 |
+
x = self.fusion_layer1(all_inputs) # [b, 4096]
|
| 666 |
+
x = nnx.relu(x)
|
| 667 |
+
balanced_fusion = self.fusion_layer2(x) # [b, 2048]
|
| 668 |
+
|
| 669 |
+
# 3. Stage-dominant: weighted combination of stage signals, then project [b, 2048]
|
| 670 |
+
gated_stage_features = jnp.concatenate([
|
| 671 |
+
sincos_encoding * gate_sincos, # [b, 1024]
|
| 672 |
+
task_stage_embedding * gate_task_stage # [b, 1024]
|
| 673 |
+
], axis=-1) # [b, 2048]
|
| 674 |
+
stage_dominant = self.stage_projection(gated_stage_features) # [b, 2048]
|
| 675 |
+
|
| 676 |
+
# 4. Pure stage: concatenate the embeddings (already 2048) [b, 2048]
|
| 677 |
+
pure_stage = jnp.concatenate([sincos_encoding, task_stage_embedding], axis=-1)
|
| 678 |
+
|
| 679 |
+
# Stack all four representations [b, 4, 2048]
|
| 680 |
+
fused_embeddings = jnp.stack([task_gated, balanced_fusion, stage_dominant, pure_stage], axis=1)
|
| 681 |
+
|
| 682 |
+
return fused_embeddings
|
| 683 |
+
|
| 684 |
+
@at.typecheck
|
| 685 |
+
def embed_prefix(
|
| 686 |
+
self,
|
| 687 |
+
obs: Observation
|
| 688 |
+
) -> tuple[
|
| 689 |
+
at.Float[at.Array, "b s emb"],
|
| 690 |
+
at.Bool[at.Array, "b s"],
|
| 691 |
+
at.Bool[at.Array, " s"]
|
| 692 |
+
]:
|
| 693 |
+
"""
|
| 694 |
+
Embed prefix: images + task + state + FAST_tokens (if provided).
|
| 695 |
+
|
| 696 |
+
Args:
|
| 697 |
+
obs: Observation (may include fast_tokens and fast_token_mask)
|
| 698 |
+
|
| 699 |
+
Returns:
|
| 700 |
+
tokens, input_mask, ar_mask
|
| 701 |
+
"""
|
| 702 |
+
input_mask = []
|
| 703 |
+
ar_mask = []
|
| 704 |
+
tokens = []
|
| 705 |
+
|
| 706 |
+
# Embed images
|
| 707 |
+
image_token_list = []
|
| 708 |
+
# Respect freeze_vision_backbone config: if frozen, always use train=False
|
| 709 |
+
# If not frozen, use the model's training state (self.deterministic)
|
| 710 |
+
vision_train_mode = (not self.deterministic) and (not self.config.freeze_vision_backbone)
|
| 711 |
+
|
| 712 |
+
for name in obs.images:
|
| 713 |
+
image_tokens, _ = self.PaliGemma.img(obs.images[name], train=vision_train_mode)
|
| 714 |
+
image_token_list.append(image_tokens) # Store for subtask prediction
|
| 715 |
+
|
| 716 |
+
tokens.append(image_tokens)
|
| 717 |
+
input_mask.append(
|
| 718 |
+
einops.repeat(
|
| 719 |
+
obs.image_masks[name],
|
| 720 |
+
"b -> b s",
|
| 721 |
+
s=image_tokens.shape[1],
|
| 722 |
+
)
|
| 723 |
+
)
|
| 724 |
+
# Image tokens attend to each other
|
| 725 |
+
ar_mask += [False] * image_tokens.shape[1]
|
| 726 |
+
|
| 727 |
+
# Add task embeddings with subtask state fusion
|
| 728 |
+
if obs.tokenized_prompt is not None:
|
| 729 |
+
# obs.tokenized_prompt now contains task_ids (shape: [batch_size, 2])
|
| 730 |
+
task_ids = obs.tokenized_prompt[:, 0] # Extract task_id: [batch_size]
|
| 731 |
+
base_task_embedding = self.task_embeddings(task_ids) # shape: [batch_size, embed_dim]
|
| 732 |
+
|
| 733 |
+
# ALWAYS use the input subtask state - never use predicted state inside model
|
| 734 |
+
if obs.tokenized_prompt.shape[1] > 1: # If we have [task_id, subtask_state]
|
| 735 |
+
subtask_state = obs.tokenized_prompt[:, 1] # Use input subtask state
|
| 736 |
+
else:
|
| 737 |
+
raise ValueError("subtask_state must be provided in tokenized_prompt for PI_BEHAVIOR model")
|
| 738 |
+
|
| 739 |
+
# Fuse task embedding with subtask state - returns [b, 4, d] with multiple representations
|
| 740 |
+
fused_task_embeddings = self.fuse_task_and_subtask(base_task_embedding, task_ids, subtask_state)
|
| 741 |
+
|
| 742 |
+
# Create task token sequence: [base_task, task_gated, balanced_fusion, stage_dominant, pure_stage]
|
| 743 |
+
task_sequence = jnp.concatenate([
|
| 744 |
+
base_task_embedding[:, None, :], # [b, 1, d] - base task token
|
| 745 |
+
fused_task_embeddings # [b, 4, d] - stage-conditioned tokens
|
| 746 |
+
], axis=1) # [b, 5, d]
|
| 747 |
+
|
| 748 |
+
tokens.append(task_sequence)
|
| 749 |
+
# All task tokens are valid
|
| 750 |
+
task_mask = jnp.ones((obs.tokenized_prompt.shape[0], 5), dtype=jnp.bool_)
|
| 751 |
+
input_mask.append(task_mask)
|
| 752 |
+
# Hierarchical attention: base task (False) then stage tokens (True, False, False, False)
|
| 753 |
+
# Base task attends to images bidirectionally
|
| 754 |
+
# Stage tokens attend to images+task but not vice versa
|
| 755 |
+
ar_mask += [False] + [True, False, False, False]
|
| 756 |
+
|
| 757 |
+
# Add state as discrete tokens (Pi05 style)
|
| 758 |
+
# Discretize state into bins
|
| 759 |
+
discretized_state = jnp.digitize(obs.state, bins=jnp.linspace(-1, 1, 256 + 1)[:-1]) - 1
|
| 760 |
+
discretized_state = jnp.clip(discretized_state, 0, 255) # Ensure valid range
|
| 761 |
+
|
| 762 |
+
# Embed each dimension of the discretized state
|
| 763 |
+
state_tokens = []
|
| 764 |
+
for i in range(obs.state.shape[-1]):
|
| 765 |
+
state_dim_tokens = self.PaliGemma.llm(discretized_state[:, i:i+1], method="embed")
|
| 766 |
+
state_tokens.append(state_dim_tokens)
|
| 767 |
+
|
| 768 |
+
if state_tokens:
|
| 769 |
+
state_tokens = jnp.concatenate(state_tokens, axis=1) # shape: [batch_size, state_dim, embed_dim]
|
| 770 |
+
tokens.append(state_tokens)
|
| 771 |
+
input_mask.append(jnp.ones((obs.state.shape[0], obs.state.shape[-1]), dtype=jnp.bool_))
|
| 772 |
+
# State tokens have full bidirectional attention with all prefix tokens
|
| 773 |
+
# (images, task, stages, and other state tokens)
|
| 774 |
+
ar_mask += [False] * state_tokens.shape[1]
|
| 775 |
+
|
| 776 |
+
# FAST tokens (from observation if provided)
|
| 777 |
+
if self.config.use_fast_auxiliary and obs.fast_tokens is not None:
|
| 778 |
+
fast_tokens = obs.fast_tokens # [B, T]
|
| 779 |
+
fast_token_mask = obs.fast_token_mask # [B, T]
|
| 780 |
+
|
| 781 |
+
# Teacher forcing: shift right [BOS, tok0, tok1, ..., tok_{T-1}]
|
| 782 |
+
bos_token = jnp.zeros((fast_tokens.shape[0], 1), dtype=jnp.int32)
|
| 783 |
+
shifted_tokens = jnp.concatenate([bos_token, fast_tokens[:, :-1]], axis=1)
|
| 784 |
+
|
| 785 |
+
# Shift mask too: [True, mask_0, mask_1, ..., mask_{T-1}]
|
| 786 |
+
bos_mask = jnp.ones((fast_tokens.shape[0], 1), dtype=jnp.bool_)
|
| 787 |
+
shifted_mask = jnp.concatenate([bos_mask, fast_token_mask[:, :-1]], axis=1)
|
| 788 |
+
|
| 789 |
+
# Embed using FAST embedding layer (NOT Paligemma!)
|
| 790 |
+
fast_token_emb = self.fast_token_embedding(shifted_tokens) # [B, T, D]
|
| 791 |
+
|
| 792 |
+
tokens.append(fast_token_emb)
|
| 793 |
+
input_mask.append(shifted_mask) # Use the actual token mask
|
| 794 |
+
# Causal for FAST: ALL tokens are causal (pure autoregressive)
|
| 795 |
+
ar_mask += [True] * shifted_tokens.shape[1]
|
| 796 |
+
|
| 797 |
+
tokens = jnp.concatenate(tokens, axis=1)
|
| 798 |
+
input_mask = jnp.concatenate(input_mask, axis=1)
|
| 799 |
+
ar_mask = jnp.array(ar_mask)
|
| 800 |
+
return tokens, input_mask, ar_mask
|
| 801 |
+
|
| 802 |
+
@at.typecheck
|
| 803 |
+
def embed_suffix(
|
| 804 |
+
self, obs: Observation, noisy_actions: _model.Actions, timestep: at.Float[at.Array, " b"]
|
| 805 |
+
) -> tuple[
|
| 806 |
+
at.Float[at.Array, "b s emb"],
|
| 807 |
+
at.Bool[at.Array, "b s"],
|
| 808 |
+
at.Bool[at.Array, " s"],
|
| 809 |
+
at.Float[at.Array, "b emb"],
|
| 810 |
+
]:
|
| 811 |
+
input_mask = []
|
| 812 |
+
ar_mask = []
|
| 813 |
+
tokens = []
|
| 814 |
+
|
| 815 |
+
# Pi05 style: no explicit state token in suffix (it's in prefix as discrete tokens)
|
| 816 |
+
|
| 817 |
+
action_tokens = self.action_in_proj(noisy_actions)
|
| 818 |
+
# Embed timestep using sine-cosine positional encoding
|
| 819 |
+
time_emb = posemb_sincos(timestep, self.action_in_proj.out_features, min_period=4e-3, max_period=4.0)
|
| 820 |
+
|
| 821 |
+
# Pi05 style: time MLP for adaRMS
|
| 822 |
+
time_emb = self.time_mlp_in(time_emb)
|
| 823 |
+
time_emb = nnx.swish(time_emb)
|
| 824 |
+
time_emb = self.time_mlp_out(time_emb)
|
| 825 |
+
time_emb = nnx.swish(time_emb)
|
| 826 |
+
action_expert_tokens = action_tokens
|
| 827 |
+
adarms_cond = time_emb
|
| 828 |
+
|
| 829 |
+
tokens.append(action_expert_tokens)
|
| 830 |
+
input_mask.append(jnp.ones(action_expert_tokens.shape[:2], dtype=jnp.bool_))
|
| 831 |
+
|
| 832 |
+
# image/task/state inputs do not attend to action tokens
|
| 833 |
+
ar_mask += [True] + ([False] * (self.action_horizon - 1))
|
| 834 |
+
|
| 835 |
+
tokens = jnp.concatenate(tokens, axis=1)
|
| 836 |
+
input_mask = jnp.concatenate(input_mask, axis=1)
|
| 837 |
+
ar_mask = jnp.array(ar_mask)
|
| 838 |
+
return tokens, input_mask, ar_mask, adarms_cond
|
| 839 |
+
|
| 840 |
+
@override
|
| 841 |
+
def compute_loss(
|
| 842 |
+
self, rng: at.KeyArrayLike, observation: Observation, actions: _model.Actions, *, train: bool = False
|
| 843 |
+
) -> at.Float[at.Array, "*b ah"]:
|
| 844 |
+
"""Not used - we only use compute_detailed_loss() for training."""
|
| 845 |
+
raise NotImplementedError("Use compute_detailed_loss() instead")
|
| 846 |
+
|
| 847 |
+
@override
|
| 848 |
+
def compute_detailed_loss(
|
| 849 |
+
self, rng: at.KeyArrayLike, observation: Observation, actions: _model.Actions, *, train: bool = False, num_flow_samples: int = 1
|
| 850 |
+
) -> dict[str, at.Float[at.Array, "*b"]]:
|
| 851 |
+
"""
|
| 852 |
+
Compute detailed loss with multiple flow matching samples.
|
| 853 |
+
|
| 854 |
+
Simplified approach using KV cache:
|
| 855 |
+
- Compute prefix KV cache once (with FAST tokens)
|
| 856 |
+
- Remove FAST tokens from cache (action expert doesn't attend to FAST)
|
| 857 |
+
- Process N flow samples independently, each reusing the same cached prefix
|
| 858 |
+
- Each sample has different noise and different time
|
| 859 |
+
- Average losses across samples
|
| 860 |
+
"""
|
| 861 |
+
losses = {}
|
| 862 |
+
|
| 863 |
+
preprocess_rng, rng = jax.random.split(rng)
|
| 864 |
+
observation = preprocess_observation(preprocess_rng, observation, train=train)
|
| 865 |
+
|
| 866 |
+
batch_size = actions.shape[0]
|
| 867 |
+
|
| 868 |
+
# 1. Embed prefix once (includes FAST tokens if provided in observation)
|
| 869 |
+
prefix_tokens, prefix_mask, prefix_ar_mask = self.embed_prefix(observation)
|
| 870 |
+
|
| 871 |
+
# 2. Compute prefix KV cache
|
| 872 |
+
prefix_attn_mask = make_attn_mask(prefix_mask, prefix_ar_mask)
|
| 873 |
+
positions_prefix = jnp.cumsum(prefix_mask, axis=1) - 1
|
| 874 |
+
(prefix_out, _), kv_cache_full = self.PaliGemma.llm(
|
| 875 |
+
[prefix_tokens, None],
|
| 876 |
+
mask=prefix_attn_mask,
|
| 877 |
+
positions=positions_prefix
|
| 878 |
+
)
|
| 879 |
+
|
| 880 |
+
# DA3 banks: timestep-independent, computed ONCE and closure-captured by the vmapped
|
| 881 |
+
# flow-sample fn (vmap broadcasts them across the N samples).
|
| 882 |
+
depth_drop_rng, rng = jax.random.split(rng)
|
| 883 |
+
spatial_banks, geom_aux_loss = self._compute_banks(
|
| 884 |
+
observation, return_aux=True, depth_drop_rng=depth_drop_rng if train else None
|
| 885 |
+
)
|
| 886 |
+
|
| 887 |
+
# 3. Predict stage from VLM output of base task token
|
| 888 |
+
# Base task token is the first token after all image tokens
|
| 889 |
+
# Image tokens all have ar_mask=False, task starts with ar_mask=False (base) then True (stage tokens)
|
| 890 |
+
# Structure: [images (all False)] [base_task (False)] [stages (True, False, False, False)]
|
| 891 |
+
# Find first True (first stage token), base task is at that index - 1
|
| 892 |
+
first_stage_token_idx = jnp.argmax(prefix_ar_mask) # Returns index of first True
|
| 893 |
+
base_task_token_idx = first_stage_token_idx - 1
|
| 894 |
+
base_task_output = prefix_out[:, base_task_token_idx, :]
|
| 895 |
+
subtask_logits = self.stage_pred_from_vlm(base_task_output) # [B, MAX_NUM_STAGES]
|
| 896 |
+
|
| 897 |
+
# Mask out invalid stages for each task (vectorized JAX operations)
|
| 898 |
+
task_ids = observation.tokenized_prompt[:, 0] # [B]
|
| 899 |
+
task_num_stages_array = jnp.array(TASK_NUM_STAGES, dtype=jnp.int32)
|
| 900 |
+
task_num_stages = task_num_stages_array[task_ids] # [B] - JAX array indexing
|
| 901 |
+
stage_range = jnp.arange(MAX_NUM_STAGES) # [15]
|
| 902 |
+
valid_mask = stage_range[None, :] < task_num_stages[:, None] # [B, 15]
|
| 903 |
+
subtask_logits = jnp.where(valid_mask, subtask_logits, -jnp.inf) # Mask invalid stages
|
| 904 |
+
|
| 905 |
+
# 4. Extract FAST loss from prefix output (before removing from cache)
|
| 906 |
+
fast_loss_value = 0.0
|
| 907 |
+
fast_len = 0
|
| 908 |
+
fast_targets = observation.fast_tokens
|
| 909 |
+
fast_token_mask = observation.fast_token_mask
|
| 910 |
+
|
| 911 |
+
if self.config.use_fast_auxiliary and fast_targets is not None:
|
| 912 |
+
fast_len = fast_targets.shape[1]
|
| 913 |
+
fast_start_idx = prefix_tokens.shape[1] - fast_len
|
| 914 |
+
fast_outputs = prefix_out[:, fast_start_idx:, :] # [B, T, D]
|
| 915 |
+
|
| 916 |
+
# Project to FAST vocab
|
| 917 |
+
fast_logits = self.fast_token_proj(fast_outputs) # [B, T, vocab_size]
|
| 918 |
+
|
| 919 |
+
# Cross-entropy loss with teacher forcing
|
| 920 |
+
pred_logits = fast_logits # [B, T, vocab]
|
| 921 |
+
target_tokens = fast_targets # [B, T]
|
| 922 |
+
loss_mask = fast_token_mask # [B, T]
|
| 923 |
+
|
| 924 |
+
log_probs = jax.nn.log_softmax(pred_logits, axis=-1)
|
| 925 |
+
target_log_probs = jnp.take_along_axis(
|
| 926 |
+
log_probs,
|
| 927 |
+
target_tokens[:, :, None],
|
| 928 |
+
axis=-1
|
| 929 |
+
).squeeze(-1) # [B, T]
|
| 930 |
+
|
| 931 |
+
fast_token_loss = -target_log_probs # [B, T]
|
| 932 |
+
|
| 933 |
+
# Apply mask and normalize by number of valid tokens
|
| 934 |
+
masked_loss = fast_token_loss * loss_mask # [B, T]
|
| 935 |
+
num_valid_tokens = jnp.maximum(jnp.sum(loss_mask, axis=-1), 1) # [B]
|
| 936 |
+
losses["fast_loss"] = jnp.sum(masked_loss, axis=-1) / num_valid_tokens # [B]
|
| 937 |
+
|
| 938 |
+
# Accuracy (only on valid tokens)
|
| 939 |
+
pred_tokens = jnp.argmax(pred_logits, axis=-1)
|
| 940 |
+
correct = (pred_tokens == target_tokens) * loss_mask
|
| 941 |
+
losses["fast_accuracy"] = jnp.sum(correct, axis=-1) / num_valid_tokens
|
| 942 |
+
|
| 943 |
+
fast_loss_value = self.config.fast_loss_weight * jnp.mean(losses["fast_loss"])
|
| 944 |
+
elif fast_targets is not None:
|
| 945 |
+
# FAST auxiliary is disabled but data contains FAST tokens
|
| 946 |
+
raise ValueError(
|
| 947 |
+
"use_fast_auxiliary=False but observation contains fast_tokens. "
|
| 948 |
+
"Either enable use_fast_auxiliary in config or ensure data doesn't contain fast_tokens."
|
| 949 |
+
)
|
| 950 |
+
|
| 951 |
+
# 5. Remove FAST tokens from KV cache (action expert doesn't attend to FAST)
|
| 952 |
+
# KV cache shape: [layers, batch, seq_len, num_kv_heads, head_dim]
|
| 953 |
+
if fast_len > 0:
|
| 954 |
+
cache_k, cache_v = kv_cache_full
|
| 955 |
+
# Remove last fast_len tokens from sequence dimension
|
| 956 |
+
cache_k = cache_k[:, :, :-fast_len, :, :]
|
| 957 |
+
cache_v = cache_v[:, :, :-fast_len, :, :]
|
| 958 |
+
kv_cache_for_actions = (cache_k, cache_v)
|
| 959 |
+
prefix_len_for_actions = prefix_tokens.shape[1] - fast_len
|
| 960 |
+
# Truncate prefix mask and ar_mask for action expert
|
| 961 |
+
prefix_mask_for_actions = prefix_mask[:, :-fast_len]
|
| 962 |
+
prefix_ar_mask_for_actions = prefix_ar_mask[:-fast_len]
|
| 963 |
+
else:
|
| 964 |
+
kv_cache_for_actions = kv_cache_full
|
| 965 |
+
prefix_len_for_actions = prefix_tokens.shape[1]
|
| 966 |
+
prefix_mask_for_actions = prefix_mask
|
| 967 |
+
prefix_ar_mask_for_actions = prefix_ar_mask
|
| 968 |
+
|
| 969 |
+
# 6. Knowledge insulation: stop gradients from action expert to VLM
|
| 970 |
+
# This must happen BEFORE kv_transform so transform still receives gradients
|
| 971 |
+
if self.config.use_knowledge_insulation:
|
| 972 |
+
kv_cache_for_actions = jax.tree.map(jax.lax.stop_gradient, kv_cache_for_actions)
|
| 973 |
+
|
| 974 |
+
# 7. Transform KV cache (after stop_gradient, so it receives action expert gradients)
|
| 975 |
+
if self.kv_transform is not None:
|
| 976 |
+
kv_cache_for_actions = self.kv_transform(kv_cache_for_actions)
|
| 977 |
+
|
| 978 |
+
# 8. Define single flow sample processing
|
| 979 |
+
def process_one_flow_sample(sample_rng):
|
| 980 |
+
"""Process one flow sample using the original cached prefix."""
|
| 981 |
+
noise_rng, time_rng = jax.random.split(sample_rng)
|
| 982 |
+
|
| 983 |
+
# Generate different noise and time for this sample
|
| 984 |
+
noise = self.generate_correlated_noise(noise_rng, batch_size)
|
| 985 |
+
time = jax.random.beta(time_rng, 1.5, 1, (batch_size,)) * 0.999 + 0.001
|
| 986 |
+
|
| 987 |
+
# Compute noisy actions and target velocity
|
| 988 |
+
time_expanded = time[:, None, None]
|
| 989 |
+
x_t = time_expanded * noise + (1 - time_expanded) * actions
|
| 990 |
+
u_t = noise - actions
|
| 991 |
+
|
| 992 |
+
# Embed suffix for this sample
|
| 993 |
+
suffix_tokens, suffix_mask, suffix_ar_mask, adarms_cond = self.embed_suffix(
|
| 994 |
+
observation, x_t, time
|
| 995 |
+
)
|
| 996 |
+
|
| 997 |
+
# Build attention mask: suffix attends to prefix (without FAST) + itself
|
| 998 |
+
# When using KV cache, mask shape should be [batch, suffix_len, prefix_len + suffix_len]
|
| 999 |
+
suffix_attn_mask = make_attn_mask(suffix_mask, suffix_ar_mask)
|
| 1000 |
+
prefix_attn_mask = einops.repeat(
|
| 1001 |
+
prefix_mask_for_actions, "b p -> b s p", s=suffix_tokens.shape[1]
|
| 1002 |
+
)
|
| 1003 |
+
full_attn_mask = jnp.concatenate([prefix_attn_mask, suffix_attn_mask], axis=-1)
|
| 1004 |
+
|
| 1005 |
+
# Positions for suffix start after cached prefix
|
| 1006 |
+
suffix_positions = prefix_len_for_actions + jnp.cumsum(suffix_mask, axis=-1) - 1
|
| 1007 |
+
|
| 1008 |
+
# Forward pass with cached prefix (discard returned cache - don't modify original!)
|
| 1009 |
+
(_, suffix_out), _ = self.PaliGemma.llm(
|
| 1010 |
+
[None, suffix_tokens],
|
| 1011 |
+
mask=full_attn_mask,
|
| 1012 |
+
positions=suffix_positions,
|
| 1013 |
+
kv_cache=kv_cache_for_actions, # Original cache, reused for all samples
|
| 1014 |
+
adarms_cond=[None, adarms_cond],
|
| 1015 |
+
banks=spatial_banks,
|
| 1016 |
+
)
|
| 1017 |
+
|
| 1018 |
+
# Compute velocity and loss
|
| 1019 |
+
action_hidden = self.apply_spatial_action_conditioning(
|
| 1020 |
+
observation,
|
| 1021 |
+
suffix_out[:, -self.action_horizon:],
|
| 1022 |
+
)
|
| 1023 |
+
v_t = self.action_out_proj(action_hidden)
|
| 1024 |
+
action_loss = jnp.square(v_t - u_t) # [B, H, D]
|
| 1025 |
+
|
| 1026 |
+
return action_loss
|
| 1027 |
+
|
| 1028 |
+
# 9. Vectorize over N flow samples
|
| 1029 |
+
# Disable type checking inside vmap (jaxtyping doesn't handle traced values well)
|
| 1030 |
+
flow_rngs = jax.random.split(rng, num_flow_samples)
|
| 1031 |
+
with at.disable_typechecking():
|
| 1032 |
+
all_action_losses = jax.vmap(process_one_flow_sample)(flow_rngs) # [N, B, H, D]
|
| 1033 |
+
|
| 1034 |
+
# 10. Average over flow samples
|
| 1035 |
+
action_loss = jnp.mean(all_action_losses, axis=0) # [B, H, D]
|
| 1036 |
+
|
| 1037 |
+
# 11. Build per-dimension action losses
|
| 1038 |
+
# Base velocity (x,y,z)
|
| 1039 |
+
losses["action_loss_base_vel_x"] = jnp.mean(action_loss[..., 0], axis=-1)
|
| 1040 |
+
losses["action_loss_base_vel_y"] = jnp.mean(action_loss[..., 1], axis=-1)
|
| 1041 |
+
losses["action_loss_base_vel_z"] = jnp.mean(action_loss[..., 2], axis=-1)
|
| 1042 |
+
|
| 1043 |
+
# Trunk joints (4)
|
| 1044 |
+
for i in range(4):
|
| 1045 |
+
losses[f"action_loss_trunk_{i}"] = jnp.mean(action_loss[..., 3+i], axis=-1)
|
| 1046 |
+
|
| 1047 |
+
# Left arm joints (7)
|
| 1048 |
+
for i in range(7):
|
| 1049 |
+
losses[f"action_loss_left_arm_{i}"] = jnp.mean(action_loss[..., 7+i], axis=-1)
|
| 1050 |
+
|
| 1051 |
+
# Left gripper
|
| 1052 |
+
losses["action_loss_left_gripper"] = jnp.mean(action_loss[..., 14], axis=-1)
|
| 1053 |
+
|
| 1054 |
+
# Right arm joints (7)
|
| 1055 |
+
for i in range(7):
|
| 1056 |
+
losses[f"action_loss_right_arm_{i}"] = jnp.mean(action_loss[..., 15+i], axis=-1)
|
| 1057 |
+
|
| 1058 |
+
# Right gripper
|
| 1059 |
+
losses["action_loss_right_gripper"] = jnp.mean(action_loss[..., 22], axis=-1)
|
| 1060 |
+
|
| 1061 |
+
# Total action loss: mean over horizon (H) and action dims (D) -> [B]
|
| 1062 |
+
losses["action_loss"] = jnp.mean(action_loss, axis=(-2, -1))
|
| 1063 |
+
|
| 1064 |
+
# 12. Add subtask loss during training
|
| 1065 |
+
subtask_loss_value = 0.0
|
| 1066 |
+
if train and observation.tokenized_prompt.shape[1] > 1:
|
| 1067 |
+
ground_truth_subtask = observation.tokenized_prompt[:, 1]
|
| 1068 |
+
subtask_loss = -jax.nn.log_softmax(subtask_logits)[
|
| 1069 |
+
jnp.arange(ground_truth_subtask.shape[0]), ground_truth_subtask
|
| 1070 |
+
]
|
| 1071 |
+
losses["subtask_loss"] = jnp.mean(subtask_loss)
|
| 1072 |
+
losses["subtask_accuracy"] = jnp.mean(
|
| 1073 |
+
jnp.argmax(subtask_logits, axis=-1) == ground_truth_subtask
|
| 1074 |
+
)
|
| 1075 |
+
subtask_loss_value = self.config.subtask_loss_weight * jnp.mean(subtask_loss)
|
| 1076 |
+
|
| 1077 |
+
# 12b. Aux geometry loss: force the perceiver tokens to carry per-sample geometry (log-depth).
|
| 1078 |
+
geom_aux_value = 0.0
|
| 1079 |
+
aux_w = getattr(getattr(self.config, "da3", None), "aux_geom_weight", 0.0)
|
| 1080 |
+
if train and aux_w > 0 and geom_aux_loss is not None:
|
| 1081 |
+
losses["geom_aux_loss"] = geom_aux_loss
|
| 1082 |
+
geom_aux_value = aux_w * geom_aux_loss
|
| 1083 |
+
|
| 1084 |
+
# 13. Total loss
|
| 1085 |
+
losses["total_loss"] = losses["action_loss"] + subtask_loss_value + fast_loss_value + geom_aux_value
|
| 1086 |
+
|
| 1087 |
+
return losses
|
| 1088 |
+
|
| 1089 |
+
@override
|
| 1090 |
+
def sample_actions(
|
| 1091 |
+
self,
|
| 1092 |
+
rng: at.KeyArrayLike,
|
| 1093 |
+
observation: Observation,
|
| 1094 |
+
*,
|
| 1095 |
+
num_steps: int | at.Int[at.Array, ""] = 20,
|
| 1096 |
+
noise: at.Float[at.Array, "b ah ad"] | None = None,
|
| 1097 |
+
initial_actions: at.Float[at.Array, "b n ad"] | None = None,
|
| 1098 |
+
prefix_tokens: at.Float[at.Array, "b p emb"] | None = None,
|
| 1099 |
+
prefix_mask: at.Bool[at.Array, "b p"] | None = None,
|
| 1100 |
+
prefix_ar_mask: at.Bool[at.Array, "b p"] | None = None,
|
| 1101 |
+
) -> _model.Actions:
|
| 1102 |
+
observation = preprocess_observation(None, observation, train=False)
|
| 1103 |
+
# Note that we use the convention more common in diffusion literature, where t=1 is noise and t=0 is the target
|
| 1104 |
+
# distribution. yes, this is the opposite of the pi0 paper, and I'm sorry.
|
| 1105 |
+
dt = -1.0 / num_steps
|
| 1106 |
+
batch_size = observation.state.shape[0]
|
| 1107 |
+
|
| 1108 |
+
# Generate or constrain noise based on inpainting requirements
|
| 1109 |
+
if initial_actions is not None:
|
| 1110 |
+
# INPAINTING PATH: Construct constrained noise z that satisfies initial_actions
|
| 1111 |
+
num_initial_actions = initial_actions.shape[1]
|
| 1112 |
+
input_action_dim = initial_actions.shape[2]
|
| 1113 |
+
|
| 1114 |
+
# Pad initial_actions to full model dimensions (32D) and action_horizon (30)
|
| 1115 |
+
if input_action_dim < self.action_dim:
|
| 1116 |
+
action_padding = jnp.zeros((batch_size, num_initial_actions, self.action_dim - input_action_dim))
|
| 1117 |
+
initial_actions_full_dim = jnp.concatenate([initial_actions, action_padding], axis=2)
|
| 1118 |
+
else:
|
| 1119 |
+
initial_actions_full_dim = initial_actions[:, :, :self.action_dim]
|
| 1120 |
+
|
| 1121 |
+
if num_initial_actions < self.action_horizon:
|
| 1122 |
+
seq_padding = jnp.zeros((batch_size, self.action_horizon - num_initial_actions, self.action_dim))
|
| 1123 |
+
initial_actions_padded = jnp.concatenate([initial_actions_full_dim, seq_padding], axis=1)
|
| 1124 |
+
else:
|
| 1125 |
+
initial_actions_padded = initial_actions_full_dim[:, :self.action_horizon]
|
| 1126 |
+
|
| 1127 |
+
# Compute O and U indices for inpainting (JIT-safe: static list comprehensions)
|
| 1128 |
+
flat_dim = self.action_horizon * self.action_dim
|
| 1129 |
+
|
| 1130 |
+
# Build O_indices: first num_initial_actions timesteps, first input_action_dim dimensions
|
| 1131 |
+
O_indices = jnp.array([
|
| 1132 |
+
t * self.action_dim + d
|
| 1133 |
+
for t in range(num_initial_actions)
|
| 1134 |
+
for d in range(input_action_dim)
|
| 1135 |
+
], dtype=jnp.int32)
|
| 1136 |
+
|
| 1137 |
+
# Build U_indices: all other indices (JIT-safe: static list comprehension)
|
| 1138 |
+
# Python set operations happen at trace time (before JIT), so this is safe
|
| 1139 |
+
O_set = {t * self.action_dim + d for t in range(num_initial_actions) for d in range(input_action_dim)}
|
| 1140 |
+
U_indices = jnp.array([
|
| 1141 |
+
i for i in range(flat_dim) if i not in O_set
|
| 1142 |
+
], dtype=jnp.int32)
|
| 1143 |
+
|
| 1144 |
+
# Generate noise
|
| 1145 |
+
rng, noise_rng = jax.random.split(rng)
|
| 1146 |
+
|
| 1147 |
+
if self.correlation_loaded:
|
| 1148 |
+
# CORRELATED NOISE: Sample with correlation matrix
|
| 1149 |
+
noise = self.generate_correlated_noise(noise_rng, batch_size)
|
| 1150 |
+
else:
|
| 1151 |
+
# FALLBACK: Independent noise
|
| 1152 |
+
noise = jax.random.normal(noise_rng, (batch_size, self.action_horizon, self.action_dim))
|
| 1153 |
+
|
| 1154 |
+
# Extract fixed z_O and x0_O for constraint enforcement
|
| 1155 |
+
noise_flat = noise.reshape(batch_size, flat_dim)
|
| 1156 |
+
fixed_z_O = noise_flat[:, O_indices] # [b, |O|] - fixed noise for inpainting
|
| 1157 |
+
x0_O = initial_actions_padded.reshape(batch_size, flat_dim)[:, O_indices] # [b, |O|] - target actions
|
| 1158 |
+
|
| 1159 |
+
# Precompute correction matrix for correlation-aware inpainting
|
| 1160 |
+
inpainting_cache = None
|
| 1161 |
+
if self.correlation_loaded:
|
| 1162 |
+
cache_key = (num_initial_actions, input_action_dim)
|
| 1163 |
+
if cache_key not in self.inpainting_cache:
|
| 1164 |
+
logger.info(f"Computing correction matrix for {num_initial_actions} steps, {input_action_dim} dims...")
|
| 1165 |
+
self.inpainting_cache[cache_key] = self._precompute_correction_matrix(O_indices, U_indices)
|
| 1166 |
+
inpainting_cache = self.inpainting_cache[cache_key]
|
| 1167 |
+
|
| 1168 |
+
else:
|
| 1169 |
+
# NO INPAINTING: Standard noise generation
|
| 1170 |
+
if noise is None:
|
| 1171 |
+
rng, noise_rng = jax.random.split(rng)
|
| 1172 |
+
noise = self.generate_correlated_noise(noise_rng, batch_size)
|
| 1173 |
+
|
| 1174 |
+
fixed_z_O = None
|
| 1175 |
+
x0_O = None
|
| 1176 |
+
O_indices = None
|
| 1177 |
+
inpainting_cache = None
|
| 1178 |
+
|
| 1179 |
+
# Split RNG for step loop
|
| 1180 |
+
rng, step_rng = jax.random.split(rng)
|
| 1181 |
+
|
| 1182 |
+
# Ensure FAST tokens are never used during inference
|
| 1183 |
+
if observation.fast_tokens is not None:
|
| 1184 |
+
raise ValueError(
|
| 1185 |
+
"FAST tokens must not be provided during inference (sample_actions). "
|
| 1186 |
+
"FAST tokens are only used during training for auxiliary loss. "
|
| 1187 |
+
"Set observation.fast_tokens=None before calling sample_actions."
|
| 1188 |
+
)
|
| 1189 |
+
|
| 1190 |
+
# Allow cache-generation callers to reuse the exact prefix embeddings
|
| 1191 |
+
# they also pool as conditioning context. The default path is unchanged
|
| 1192 |
+
# for policy inference and existing checkpoints.
|
| 1193 |
+
supplied_prefix = (
|
| 1194 |
+
prefix_tokens is not None,
|
| 1195 |
+
prefix_mask is not None,
|
| 1196 |
+
prefix_ar_mask is not None,
|
| 1197 |
+
)
|
| 1198 |
+
if any(supplied_prefix) and not all(supplied_prefix):
|
| 1199 |
+
raise ValueError(
|
| 1200 |
+
"prefix_tokens, prefix_mask, and prefix_ar_mask must be supplied together"
|
| 1201 |
+
)
|
| 1202 |
+
if prefix_tokens is None:
|
| 1203 |
+
prefix_tokens, prefix_mask, prefix_ar_mask = self.embed_prefix(observation)
|
| 1204 |
+
|
| 1205 |
+
# First fill KV cache with a forward pass of the prefix (no FAST tokens during inference)
|
| 1206 |
+
prefix_attn_mask = make_attn_mask(prefix_mask, prefix_ar_mask)
|
| 1207 |
+
positions = jnp.cumsum(prefix_mask, axis=1) - 1
|
| 1208 |
+
(prefix_out, _), kv_cache = self.PaliGemma.llm([prefix_tokens, None], mask=prefix_attn_mask, positions=positions)
|
| 1209 |
+
|
| 1210 |
+
# DA3 banks: computed once, reused across all denoise steps.
|
| 1211 |
+
spatial_banks = self._compute_banks(observation)
|
| 1212 |
+
|
| 1213 |
+
# Predict stage from VLM output of base task token
|
| 1214 |
+
# Find base task token position (same logic as in compute_detailed_loss)
|
| 1215 |
+
first_stage_token_idx = jnp.argmax(prefix_ar_mask) # Returns index of first True
|
| 1216 |
+
base_task_token_idx = first_stage_token_idx - 1
|
| 1217 |
+
base_task_output = prefix_out[:, base_task_token_idx, :]
|
| 1218 |
+
subtask_logits = self.stage_pred_from_vlm(base_task_output) # [B, MAX_NUM_STAGES]
|
| 1219 |
+
|
| 1220 |
+
# Mask out invalid stages for each task (vectorized JAX operations)
|
| 1221 |
+
task_ids = observation.tokenized_prompt[:, 0] # [B]
|
| 1222 |
+
task_num_stages_array = jnp.array(TASK_NUM_STAGES, dtype=jnp.int32)
|
| 1223 |
+
task_num_stages = task_num_stages_array[task_ids] # [B] - JAX array indexing
|
| 1224 |
+
stage_range = jnp.arange(MAX_NUM_STAGES) # [15]
|
| 1225 |
+
valid_mask = stage_range[None, :] < task_num_stages[:, None] # [B, 15]
|
| 1226 |
+
subtask_logits = jnp.where(valid_mask, subtask_logits, -jnp.inf)
|
| 1227 |
+
|
| 1228 |
+
# Transform KV cache for cross-layer attention
|
| 1229 |
+
if self.kv_transform is not None:
|
| 1230 |
+
kv_cache = self.kv_transform(kv_cache)
|
| 1231 |
+
|
| 1232 |
+
def step(carry):
|
| 1233 |
+
x_t, time, step_rng = carry
|
| 1234 |
+
|
| 1235 |
+
# Use config value for time threshold
|
| 1236 |
+
TIME_THRESHOLD_INPAINT = self.config.time_threshold_inpaint
|
| 1237 |
+
|
| 1238 |
+
# Model forward pass
|
| 1239 |
+
suffix_tokens, suffix_mask, suffix_ar_mask, adarms_cond = self.embed_suffix(
|
| 1240 |
+
observation, x_t, jnp.broadcast_to(time, batch_size)
|
| 1241 |
+
)
|
| 1242 |
+
suffix_attn_mask = make_attn_mask(suffix_mask, suffix_ar_mask)
|
| 1243 |
+
prefix_attn_mask = einops.repeat(prefix_mask, "b p -> b s p", s=suffix_tokens.shape[1])
|
| 1244 |
+
full_attn_mask = jnp.concatenate([prefix_attn_mask, suffix_attn_mask], axis=-1)
|
| 1245 |
+
assert full_attn_mask.shape == (
|
| 1246 |
+
batch_size,
|
| 1247 |
+
suffix_tokens.shape[1],
|
| 1248 |
+
prefix_tokens.shape[1] + suffix_tokens.shape[1],
|
| 1249 |
+
)
|
| 1250 |
+
positions = jnp.sum(prefix_mask, axis=-1)[:, None] + jnp.cumsum(suffix_mask, axis=-1) - 1
|
| 1251 |
+
|
| 1252 |
+
(prefix_out, suffix_out), _ = self.PaliGemma.llm(
|
| 1253 |
+
[None, suffix_tokens],
|
| 1254 |
+
mask=full_attn_mask,
|
| 1255 |
+
positions=positions,
|
| 1256 |
+
kv_cache=kv_cache,
|
| 1257 |
+
adarms_cond=[None, adarms_cond],
|
| 1258 |
+
banks=spatial_banks,
|
| 1259 |
+
)
|
| 1260 |
+
assert prefix_out is None
|
| 1261 |
+
action_hidden = self.apply_spatial_action_conditioning(
|
| 1262 |
+
observation,
|
| 1263 |
+
suffix_out[:, -self.action_horizon :],
|
| 1264 |
+
)
|
| 1265 |
+
v_t = self.action_out_proj(action_hidden)
|
| 1266 |
+
|
| 1267 |
+
# Euler step: x_{t+dt} = x_t + dt * v_t
|
| 1268 |
+
x_t_new = x_t + dt * v_t
|
| 1269 |
+
|
| 1270 |
+
# Apply correlation-aware inpainting correction
|
| 1271 |
+
# Only enforce when time > TIME_THRESHOLD_INPAINT (let model be free in final steps)
|
| 1272 |
+
if fixed_z_O is not None:
|
| 1273 |
+
time_new = time + dt
|
| 1274 |
+
|
| 1275 |
+
def apply_correlated_correction(x):
|
| 1276 |
+
x_flat = x.reshape(batch_size, -1)
|
| 1277 |
+
|
| 1278 |
+
# Compute desired state at O: x_t[O] = (1-t)*x0[O] + t*z_O
|
| 1279 |
+
x_desired_O = (1.0 - time_new) * x0_O + time_new * fixed_z_O # [b, |O|]
|
| 1280 |
+
|
| 1281 |
+
# Compute correction at O
|
| 1282 |
+
delta_O = x_desired_O - x_flat[:, O_indices] # [b, |O|]
|
| 1283 |
+
|
| 1284 |
+
# Apply hard constraint at O
|
| 1285 |
+
x_flat = x_flat.at[:, O_indices].set(x_desired_O)
|
| 1286 |
+
|
| 1287 |
+
# If correlation matrix available, propagate correction to U
|
| 1288 |
+
if inpainting_cache is not None:
|
| 1289 |
+
correction_matrix = inpainting_cache['correction_matrix'] # [|U|, |O|]
|
| 1290 |
+
U_indices_cached = inpainting_cache['U_indices']
|
| 1291 |
+
|
| 1292 |
+
# Compute correlated correction: δ_U = Σ_{UO}Σ_{OO}^{-1} @ δ_O
|
| 1293 |
+
delta_U = delta_O @ correction_matrix.T # [b, |U|]
|
| 1294 |
+
|
| 1295 |
+
# Skip if correction too large (indicates instability)
|
| 1296 |
+
max_correction = jnp.max(jnp.abs(delta_U))
|
| 1297 |
+
x_flat = jax.lax.cond(
|
| 1298 |
+
# Prevents exploding corrections in case of noisy out of distribution initial actions
|
| 1299 |
+
max_correction <= 1.0,
|
| 1300 |
+
lambda x: x.at[:, U_indices_cached].add(delta_U),
|
| 1301 |
+
lambda x: x,
|
| 1302 |
+
x_flat
|
| 1303 |
+
)
|
| 1304 |
+
|
| 1305 |
+
# Sanity check: if Σ = I, correction_matrix = 0, so delta_U = 0 that is correct
|
| 1306 |
+
# If the correlation is 1 everywhere we will go to the flat prediction that is correct
|
| 1307 |
+
|
| 1308 |
+
return x_flat.reshape(batch_size, self.action_horizon, self.action_dim)
|
| 1309 |
+
|
| 1310 |
+
# Only apply correction when NEW time > threshold
|
| 1311 |
+
x_t_new = jax.lax.cond(
|
| 1312 |
+
time_new > TIME_THRESHOLD_INPAINT,
|
| 1313 |
+
apply_correlated_correction,
|
| 1314 |
+
lambda x: x,
|
| 1315 |
+
x_t_new
|
| 1316 |
+
)
|
| 1317 |
+
|
| 1318 |
+
return x_t_new, time + dt, step_rng
|
| 1319 |
+
|
| 1320 |
+
def cond(carry):
|
| 1321 |
+
x_t, time, step_rng = carry
|
| 1322 |
+
# Robust to floating-point error
|
| 1323 |
+
return time >= -dt / 2
|
| 1324 |
+
|
| 1325 |
+
x_0, _, _ = jax.lax.while_loop(cond, step, (noise, 1.0, step_rng))
|
| 1326 |
+
|
| 1327 |
+
return x_0, subtask_logits
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/pi_behavior_config.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PI_BEHAVIOR Model Configuration
|
| 2 |
+
|
| 3 |
+
Configuration for PI_BEHAVIOR model on BEHAVIOR-1K challenge.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import dataclasses
|
| 7 |
+
import json
|
| 8 |
+
import pathlib
|
| 9 |
+
from typing import TYPE_CHECKING
|
| 10 |
+
|
| 11 |
+
import flax.nnx as nnx
|
| 12 |
+
import jax
|
| 13 |
+
import jax.numpy as jnp
|
| 14 |
+
from typing_extensions import override
|
| 15 |
+
|
| 16 |
+
from openpi.models import model as _model
|
| 17 |
+
from openpi.models import gemma as _gemma
|
| 18 |
+
from openpi.shared import array_typing as at
|
| 19 |
+
import openpi.shared.nnx_utils as nnx_utils
|
| 20 |
+
|
| 21 |
+
from b1k.models.observation import Observation
|
| 22 |
+
|
| 23 |
+
if TYPE_CHECKING:
|
| 24 |
+
from b1k.models.pi_behavior import PiBehavior
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# Per-task stage counts (based on avg_episode_length / 900, capped between 5-15)
|
| 28 |
+
# Use tuple for immutability and to avoid JAX device allocation at import time
|
| 29 |
+
TASK_NUM_STAGES = (
|
| 30 |
+
5, 6, 15, 15, 14, 12, 9, 15, 10, 15, # Tasks 0-9
|
| 31 |
+
7, 13, 10, 15, 15, 15, 15, 11, 13, 12, # Tasks 10-19
|
| 32 |
+
14, 15, 9, 15, 15, 15, 15, 15, 15, 15, # Tasks 20-29
|
| 33 |
+
11, 10, 10, 13, 5, 5, 14, 6, 8, 10, # Tasks 30-39
|
| 34 |
+
5, 15, 8, 15, 12, 11, 9, 14, 15, 15, # Tasks 40-49
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
MAX_NUM_STAGES = 15 # Maximum stages per task
|
| 38 |
+
TOTAL_TASK_STAGE_EMBEDDINGS = sum(TASK_NUM_STAGES) # 596 total embeddings
|
| 39 |
+
|
| 40 |
+
# Cumulative offsets for indexing into task_stage_embeddings (as tuple)
|
| 41 |
+
TASK_STAGE_OFFSETS = tuple([0] + [sum(TASK_NUM_STAGES[:i+1]) for i in range(len(TASK_NUM_STAGES) - 1)])
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@dataclasses.dataclass(frozen=True)
|
| 45 |
+
class B1KDA3Config:
|
| 46 |
+
"""DA3 spatial-language branch for PiBehavior (inline extraction; b1k cameras are square)."""
|
| 47 |
+
|
| 48 |
+
enabled: bool = True
|
| 49 |
+
num_views: int = 3 # zed head (main), left/right realsense (wrist branches)
|
| 50 |
+
da3_channels: int = 1536 # GIANT embed dim
|
| 51 |
+
da3_layers: int = 4 # out_layers (19, 26, 33, 39)
|
| 52 |
+
grid_hw: tuple[int, int] = (18, 18) # 252x252 square DA3 input / patch 14
|
| 53 |
+
hidden_dim: int = 1024 # == action-expert width
|
| 54 |
+
lang_dim: int = 1024 # ModernBERT-large (task-name embeddings)
|
| 55 |
+
lang_max_len: int = 32
|
| 56 |
+
num_heads: int = 8
|
| 57 |
+
lang_fusion_depth: int = 2
|
| 58 |
+
num_inject_layers: int = 6 # last 6 of 18 action-expert blocks
|
| 59 |
+
spatial_scale: float = 2.0
|
| 60 |
+
# V2 "force-spatial-on" defaults (now that geometry is CORRECT). Zero-init lets the model learn to
|
| 61 |
+
# IGNORE spatial (image path fits first, no gradient left to turn the injection on). Nonzero init +
|
| 62 |
+
# per-head logit-gain keep the injection ACTIVE and the attention SHARP/learnable from step 0, so the
|
| 63 |
+
# model must account for the (now-sane) banks. This only hurt before because geometry was garbage.
|
| 64 |
+
spatial_init_std: float = 0.01
|
| 65 |
+
attn_logit_gain: bool = True
|
| 66 |
+
attn_logit_gain_init: float = 3.0 # retuned for qk_norm: 20 eff tokens of 324
|
| 67 |
+
attn_logit_gain_max: float = 8.0 # gain 8 -> 2.5 eff tokens; hard ceiling
|
| 68 |
+
bank_token_embed: bool = True
|
| 69 |
+
perceiver_query_std: float = 0.05
|
| 70 |
+
# Perceiver-collapse fixes. Default False preserves the arch of existing checkpoints.
|
| 71 |
+
# root cause: random-init queries -> q.k ~ 0 -> near-uniform softmax over 432 patches
|
| 72 |
+
# -> every query reads the same mean(V) AND dL/dQ,K is starved (~1/432) so queries never
|
| 73 |
+
# train; the shared output (||.||~500) then swamps query identity (||q||~1.6) ~300:1.
|
| 74 |
+
perceiver_logit_gain: bool = False # sharpen attention at init -> diverse reads + live Q/K grads
|
| 75 |
+
# --- 2026-07-22 attention-saturation fixes (see DA3_ATTENTION_SATURATION.md) ---
|
| 76 |
+
qk_norm: bool = True # per-head RMSNorm on Q,K before the dot product
|
| 77 |
+
perceiver_norm_out: bool = True # LayerNorm the perceiver output (was amplifying x1900)
|
| 78 |
+
pos_emb_scale: float = 0.25 # constant pos_emb was rms 5.03 vs signal 4.38
|
| 79 |
+
perceiver_logit_gain_init: float = 3.0 # retuned for qk_norm (was 8 -> 2.5 eff tokens)
|
| 80 |
+
perceiver_logit_gain_max: float = 8.0
|
| 81 |
+
perceiver_norm_attn_out: bool = False # LN attn-out before residual -> query identity survives
|
| 82 |
+
# --- 2026-07-23 constant-collapse fix (see b1k-da3-frozenbase-verdict) ---
|
| 83 |
+
# The bank was measured ~90% learned-constant (view/pos/lang/bank_token embeds) vs ~10% per-sample
|
| 84 |
+
# DA3 content; the frozen base latched onto the constant (net-harmful: zeroing the bank cut loss 92%)
|
| 85 |
+
# and never used geometry (shuffling banks across samples moved loss +0.2%). bank_center projects out
|
| 86 |
+
# the batch-mean so a constant injects EXACTLY zero -- only per-sample deviation survives, forcing the
|
| 87 |
+
# model to use geometry or nothing. NOTE: like batchnorm, needs bs>1; deploy at bs=1 needs an EMA of
|
| 88 |
+
# the mean (TODO) -- the current-batch projection is for the "does geometry get used" experiment.
|
| 89 |
+
bank_center: bool = False
|
| 90 |
+
# --- 2026-07-23 aux geometry loss ---
|
| 91 |
+
# Decode the perceiver token output back to per-patch log-depth (grid-pos queries attend the K
|
| 92 |
+
# perceiver tokens). MSE against the DA3 depth FORCES the perceiver output to carry per-sample
|
| 93 |
+
# geometry regardless of the action loss's incentive -- the guaranteed fix for "geometry unused".
|
| 94 |
+
aux_geom_head: bool = False # build the decoder head
|
| 95 |
+
aux_geom_weight: float = 0.0 # weight of the log-depth MSE in the total loss
|
| 96 |
+
# Zero the log-depth INPUT channel (ray7 ch 6) so depth is target-only. Without this the aux task
|
| 97 |
+
# is circular (depth in -> depth out, a trivial autoencoder); with it, predicting depth REQUIRES
|
| 98 |
+
# reading it out of the DA3 features. Shape-compatible (channel zeroed, not removed).
|
| 99 |
+
depth_target_only: bool = False
|
| 100 |
+
# --- 2026-07-23 K/V split (address/payload separation in the perceiver) ---
|
| 101 |
+
# payload (values) = DA3 latents + depth encoding; address (keys only) = pos_emb + ray_emb +
|
| 102 |
+
# view_emb. Addresses steer routing but are structurally excluded from the value stream, so an
|
| 103 |
+
# input-independent constant can no longer flow into (and dominate) the bank. depth_dropout
|
| 104 |
+
# zeroes the depth encoding for that fraction of training samples so the DA3 features must carry
|
| 105 |
+
# geometry redundantly. NOTE: kv_split changes the spatial arch (ray_mlp 7ch -> 6ch + depth_mlp);
|
| 106 |
+
# spatial params are NOT checkpoint-compatible across this flag.
|
| 107 |
+
kv_split: bool = False
|
| 108 |
+
depth_dropout: float = 0.0
|
| 109 |
+
# --- 2026-07-24 spatial-bank upgrades ---
|
| 110 |
+
# perc_locality: anchor each perceiver query to a grid region with a learnable -gamma*dist^2 logit
|
| 111 |
+
# bias, so tokens are LOCAL descriptors (fixes over-averaging) instead of global scene means.
|
| 112 |
+
# cross_view: after the per-view perceivers, add a camera-pose embed and self-attend across the
|
| 113 |
+
# concatenated view tokens so the three views fuse into one 3D scene (then split back per view).
|
| 114 |
+
perc_locality: bool = False
|
| 115 |
+
cross_view: bool = False
|
| 116 |
+
cross_view_depth: int = 2
|
| 117 |
+
bank_token_embed_query: bool = True # False = old post-fusion placement (faithful eval of old ckpts)
|
| 118 |
+
# --- VGGT-Omega enrichments (v2): extra bank inputs harvested from the VGGT forward; all no-ops
|
| 119 |
+
# unless the loader is the VGGT extractor (which supplies da3_depth_conf/pose_enc/cam_tokens). ---
|
| 120 |
+
use_depth_conf: bool = False # add VGGT depth confidence as a payload reliability channel
|
| 121 |
+
use_pose_enc: bool = False # add VGGT pose encoding to the cross-view camera feature
|
| 122 |
+
use_cam_tokens: bool = False # append VGGT camera+register tokens as global bank tokens
|
| 123 |
+
cam_token_dim: int = 2048 # channel width of da3_cam_tokens (VGGT 2*embed_dim)
|
| 124 |
+
pose_enc_dim: int = 9 # VGGT pose_enc width (trans3+quat4+fov2)
|
| 125 |
+
feat_input_norm: bool = False # LayerNorm raw backbone feats before projection (tames VGGT outliers)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
@dataclasses.dataclass(frozen=True)
|
| 129 |
+
class PiBehaviorConfig(_model.BaseModelConfig):
|
| 130 |
+
dtype: str = "bfloat16"
|
| 131 |
+
paligemma_variant: _gemma.Variant = "gemma_2b"
|
| 132 |
+
action_expert_variant: _gemma.Variant = "gemma_300m"
|
| 133 |
+
|
| 134 |
+
# Set the model specific defaults.
|
| 135 |
+
action_dim: int = 32
|
| 136 |
+
action_horizon: int = 30
|
| 137 |
+
max_token_len: int = 200 # Only used for compatibility, not for actual tokenization
|
| 138 |
+
|
| 139 |
+
# Number of tasks in the behavior dataset
|
| 140 |
+
num_tasks: int = 50
|
| 141 |
+
# Task embedding dimension - will match the paligemma width
|
| 142 |
+
task_embedding_dim: int = None # type: ignore
|
| 143 |
+
# Maximum number of subtask states across all tasks
|
| 144 |
+
max_num_subtask_states: int = MAX_NUM_STAGES
|
| 145 |
+
|
| 146 |
+
# Path to task data JSON file for initialization
|
| 147 |
+
task_data_path: str = "b1k/BEHAVIOR-1K/docs/challenge/task_data.json"
|
| 148 |
+
|
| 149 |
+
# Whether to use correlated noise matching action covariance structure
|
| 150 |
+
# Requires correlation matrix in norm_stats (computed by compute_norm_stats.py)
|
| 151 |
+
use_correlated_noise: bool = True
|
| 152 |
+
|
| 153 |
+
# Shrinkage parameter for correlation regularization
|
| 154 |
+
# Applied as: S_regularized = beta * S + (1-beta) * I
|
| 155 |
+
# beta=1.0 means full correlation (no shrinkage)
|
| 156 |
+
# beta=0.7 means 70% correlation + 30% independence (recommended for robustness)
|
| 157 |
+
# beta=0.0 means independence (no correlation)
|
| 158 |
+
correlation_beta: float = 0.5
|
| 159 |
+
|
| 160 |
+
# FAST auxiliary training configuration
|
| 161 |
+
use_fast_auxiliary: bool = False # Enable FAST during training
|
| 162 |
+
fast_loss_weight: float = 0.1 # Weight for FAST loss (vs flow loss)
|
| 163 |
+
|
| 164 |
+
# Action dimensions to encode with FAST (default: 0:6, 7:23 = 22 dims)
|
| 165 |
+
# Format: "0:6,7:23" or list of tuples [(0, 6), (7, 23)]
|
| 166 |
+
fast_encoded_dims: str | list[tuple[int, int]] = "0:6,7:23"
|
| 167 |
+
|
| 168 |
+
# FAST tokenizer vocab size
|
| 169 |
+
fast_vocab_size: int = 1024
|
| 170 |
+
|
| 171 |
+
# Max FAST tokens to predict (truncate if exceeded)
|
| 172 |
+
max_fast_tokens: int = 32
|
| 173 |
+
|
| 174 |
+
# FAST tokenizer path (set during initialization, relative to assets_dir/asset_id)
|
| 175 |
+
fast_tokenizer_path: str | None = None
|
| 176 |
+
|
| 177 |
+
# KV cache transformation for cross-layer attention between VLM and action expert
|
| 178 |
+
# Allows each action expert layer to attend to a learned combination of all VLM layers
|
| 179 |
+
use_kv_transform: bool = True
|
| 180 |
+
|
| 181 |
+
# Knowledge insulation: stop action expert gradients from flowing to VLM backbone
|
| 182 |
+
# VLM trains on FAST tokens only, action expert on flow matching with frozen VLM features
|
| 183 |
+
# Implements approach from https://www.physicalintelligence.company/research/knowledge_insulation
|
| 184 |
+
use_knowledge_insulation: bool = True
|
| 185 |
+
|
| 186 |
+
# Subtask/stage prediction auxiliary loss weight (relative to action loss)
|
| 187 |
+
# Higher values emphasize stage prediction accuracy at the expense of action quality
|
| 188 |
+
subtask_loss_weight: float = 0.1
|
| 189 |
+
|
| 190 |
+
# Time threshold for inpainting during inference
|
| 191 |
+
# Stop enforcing inpainting constraint when t < threshold (let model be free in final steps)
|
| 192 |
+
time_threshold_inpaint: float = 0.3
|
| 193 |
+
|
| 194 |
+
# Vision backbone finetuning control
|
| 195 |
+
freeze_vision_backbone: bool = True
|
| 196 |
+
|
| 197 |
+
# DA3 spatial-language adapter. The DA3/ModernBERT branch is computed
|
| 198 |
+
# offline and supplied as tokens in Observation.spatial_tokens.
|
| 199 |
+
use_spatial_action_cross_attention: bool = False
|
| 200 |
+
spatial_token_dim: int = 1024
|
| 201 |
+
spatial_num_tokens: int = 320 # DA3 perc bank default: 128 + 96 + 96
|
| 202 |
+
spatial_num_heads: int = 8
|
| 203 |
+
spatial_residual_scale: float = 1.0
|
| 204 |
+
|
| 205 |
+
# Full DA3 spatial-language branch (supersedes the flat spatial_tokens adapter above):
|
| 206 |
+
# frozen DA3-GIANT runs INLINE in the data pipeline; the trainable bank builder + method-B
|
| 207 |
+
# cross-attention injection (action-expert layers 12-17) live in the model. Proven on RoboReal.
|
| 208 |
+
da3: "B1KDA3Config | None" = None
|
| 209 |
+
|
| 210 |
+
def __post_init__(self):
|
| 211 |
+
if self.task_embedding_dim is None:
|
| 212 |
+
paligemma_config = _gemma.get_config(self.paligemma_variant)
|
| 213 |
+
object.__setattr__(self, "task_embedding_dim", paligemma_config.width)
|
| 214 |
+
|
| 215 |
+
def get_fast_dim_ranges(self) -> list[tuple[int, int]]:
|
| 216 |
+
"""Parse fast_encoded_dims into list of ranges."""
|
| 217 |
+
if isinstance(self.fast_encoded_dims, str):
|
| 218 |
+
ranges = []
|
| 219 |
+
for range_str in self.fast_encoded_dims.split(','):
|
| 220 |
+
start, end = map(int, range_str.strip().split(':'))
|
| 221 |
+
ranges.append((start, end))
|
| 222 |
+
return ranges
|
| 223 |
+
return self.fast_encoded_dims
|
| 224 |
+
|
| 225 |
+
def get_total_fast_dims(self) -> int:
|
| 226 |
+
"""Get total number of dimensions encoded by FAST."""
|
| 227 |
+
return sum(end - start for start, end in self.get_fast_dim_ranges())
|
| 228 |
+
|
| 229 |
+
@property
|
| 230 |
+
@override
|
| 231 |
+
def model_type(self):
|
| 232 |
+
return "pi_behavior"
|
| 233 |
+
|
| 234 |
+
@override
|
| 235 |
+
def create(self, rng: at.KeyArrayLike) -> "PiBehavior":
|
| 236 |
+
from b1k.models.pi_behavior import PiBehavior
|
| 237 |
+
|
| 238 |
+
return PiBehavior(self, rngs=nnx.Rngs(rng))
|
| 239 |
+
|
| 240 |
+
@override
|
| 241 |
+
def inputs_spec(self, *, batch_size: int = 1) -> tuple["Observation", _model.Actions]:
|
| 242 |
+
image_spec = jax.ShapeDtypeStruct([batch_size, *_model.IMAGE_RESOLUTION, 3], jnp.float32)
|
| 243 |
+
image_mask_spec = jax.ShapeDtypeStruct([batch_size], jnp.bool_)
|
| 244 |
+
|
| 245 |
+
with at.disable_typechecking():
|
| 246 |
+
obs_kwargs = {
|
| 247 |
+
"images": {
|
| 248 |
+
"base_0_rgb": image_spec,
|
| 249 |
+
"left_wrist_0_rgb": image_spec,
|
| 250 |
+
"right_wrist_0_rgb": image_spec,
|
| 251 |
+
},
|
| 252 |
+
"image_masks": {
|
| 253 |
+
"base_0_rgb": image_mask_spec,
|
| 254 |
+
"left_wrist_0_rgb": image_mask_spec,
|
| 255 |
+
"right_wrist_0_rgb": image_mask_spec,
|
| 256 |
+
},
|
| 257 |
+
"state": jax.ShapeDtypeStruct([batch_size, self.action_dim], jnp.float32),
|
| 258 |
+
"tokenized_prompt": jax.ShapeDtypeStruct([batch_size, 2], jnp.int32),
|
| 259 |
+
"tokenized_prompt_mask": jax.ShapeDtypeStruct([batch_size, 2], bool),
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
if self.use_fast_auxiliary:
|
| 263 |
+
obs_kwargs["fast_tokens"] = jax.ShapeDtypeStruct([batch_size, self.max_fast_tokens], jnp.int32)
|
| 264 |
+
obs_kwargs["fast_token_mask"] = jax.ShapeDtypeStruct([batch_size, self.max_fast_tokens], bool)
|
| 265 |
+
|
| 266 |
+
if self.da3 is not None and self.da3.enabled:
|
| 267 |
+
d = self.da3
|
| 268 |
+
gh, gw = d.grid_hw
|
| 269 |
+
obs_kwargs["da3_features"] = jax.ShapeDtypeStruct(
|
| 270 |
+
[batch_size, d.da3_layers, d.num_views, d.da3_channels, gh, gw], jnp.uint16
|
| 271 |
+
)
|
| 272 |
+
obs_kwargs["da3_ray"] = jax.ShapeDtypeStruct([batch_size, d.num_views, 3, gh, gw], jnp.float32)
|
| 273 |
+
obs_kwargs["da3_depth"] = jax.ShapeDtypeStruct([batch_size, d.num_views, 1, gh, gw], jnp.float32)
|
| 274 |
+
obs_kwargs["camera_extrinsics"] = jax.ShapeDtypeStruct([batch_size, d.num_views, 4, 4], jnp.float32)
|
| 275 |
+
obs_kwargs["lang_feat"] = jax.ShapeDtypeStruct([batch_size, d.lang_max_len, d.lang_dim], jnp.float32)
|
| 276 |
+
obs_kwargs["lang_mask"] = jax.ShapeDtypeStruct([batch_size, d.lang_max_len], bool)
|
| 277 |
+
|
| 278 |
+
if self.use_spatial_action_cross_attention:
|
| 279 |
+
obs_kwargs["spatial_tokens"] = jax.ShapeDtypeStruct(
|
| 280 |
+
[batch_size, self.spatial_num_tokens, self.spatial_token_dim],
|
| 281 |
+
jnp.float32,
|
| 282 |
+
)
|
| 283 |
+
obs_kwargs["spatial_token_mask"] = jax.ShapeDtypeStruct(
|
| 284 |
+
[batch_size, self.spatial_num_tokens],
|
| 285 |
+
bool,
|
| 286 |
+
)
|
| 287 |
+
|
| 288 |
+
observation_spec = Observation(**obs_kwargs)
|
| 289 |
+
|
| 290 |
+
action_spec = jax.ShapeDtypeStruct([batch_size, self.action_horizon, self.action_dim], jnp.float32)
|
| 291 |
+
return observation_spec, action_spec
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/spatial_da3.py
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""DA3 spatial-language branch for pi0.5 (JAX/Flax nnx port of the X-VLA addon).
|
| 2 |
+
|
| 3 |
+
Faithful reimplementation of the TRAINABLE modules from
|
| 4 |
+
`DA3-XVLA-cache/models/spatial_language.py` (H=1024, GIANT C=1536, grid 18x24=432,
|
| 5 |
+
perceiver tokens 128/96/96, 7-ch scale-aware ray, ModernBERT language fusion).
|
| 6 |
+
|
| 7 |
+
The FROZEN DA3 backbone + ModernBERT run offline (features precached); this module
|
| 8 |
+
consumes their outputs as arrays and produces per-view "banks" that are cross-attended
|
| 9 |
+
into the action-expert's late blocks (see gemma.py `SpatialActionInjection`).
|
| 10 |
+
|
| 11 |
+
Only the bank BUILDER lives here (nnx, a submodule of Pi0). The injection layer lives
|
| 12 |
+
in gemma.py (linen, inside the action-expert scan). Both use identical X-VLA math.
|
| 13 |
+
|
| 14 |
+
Reference math (verified by the understand-phase spec):
|
| 15 |
+
- ResidualCrossAttention: out = q_hidden + scale * MHA(LN_q(q_hidden), LN_kv(kv), LN_kv(kv))
|
| 16 |
+
- MHA matches torch nn.MultiheadAttention: separate q/k/v/out Linears w/ bias, 1/sqrt(head_dim).
|
| 17 |
+
- GELU is the tanh approximation everywhere; LayerNorm eps=1e-5.
|
| 18 |
+
- Perceiver residual adds the RAW learned query (not the normalized one).
|
| 19 |
+
- View order everywhere: 0=main/countertop, 1=left wrist, 2=right wrist.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import math as _math
|
| 23 |
+
|
| 24 |
+
import einops
|
| 25 |
+
import flax.nnx as nnx
|
| 26 |
+
import jax
|
| 27 |
+
import jax.numpy as jnp
|
| 28 |
+
|
| 29 |
+
import openpi.shared.array_typing as at
|
| 30 |
+
|
| 31 |
+
# ---------------------------------------------------------------------------
|
| 32 |
+
# primitives
|
| 33 |
+
# ---------------------------------------------------------------------------
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _gelu(x):
|
| 37 |
+
return nnx.gelu(x, approximate=True) # tanh approximation (matches torch GELU(approximate="tanh"))
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class MHACrossAttn(nnx.Module):
|
| 41 |
+
"""Multi-head cross-attention matching torch nn.MultiheadAttention math (no residual, no norm)."""
|
| 42 |
+
|
| 43 |
+
def __init__(self, dim: int, num_heads: int, *, logit_gain: bool = False,
|
| 44 |
+
logit_gain_init: float = 32.0, logit_gain_max: float = 16.0,
|
| 45 |
+
qk_norm: bool = False, rngs: nnx.Rngs):
|
| 46 |
+
assert dim % num_heads == 0
|
| 47 |
+
self.num_heads = num_heads
|
| 48 |
+
self.head_dim = dim // num_heads
|
| 49 |
+
self.q_proj = nnx.Linear(dim, dim, rngs=rngs)
|
| 50 |
+
self.k_proj = nnx.Linear(dim, dim, rngs=rngs)
|
| 51 |
+
self.v_proj = nnx.Linear(dim, dim, rngs=rngs)
|
| 52 |
+
self.out_proj = nnx.Linear(dim, dim, rngs=rngs)
|
| 53 |
+
# QK-NORM: per-head RMSNorm on Q and K BEFORE the dot product. Measured at step 10k without
|
| 54 |
+
# it: raw |logit| reached 6653 (normal is O(1-10)), softmax saturated to one-hot
|
| 55 |
+
# (entropy 0.007 vs uniform 5.78, effective tokens attended = 1.0/324, max prob 0.997).
|
| 56 |
+
# A saturated softmax has a vanishing Jacobian, so the attention pattern then FREEZES and
|
| 57 |
+
# cannot recover. Nothing else bounds logit scale here: q_proj/k_proj grow freely under the
|
| 58 |
+
# high-LR 'core' group with weight_decay 1e-10. Normalizing Q,K to unit RMS caps
|
| 59 |
+
# |q.k|/sqrt(head_dim) at O(1) structurally, no matter how large the projections get --
|
| 60 |
+
# which also makes logit_gain behave as the temperature it was meant to be.
|
| 61 |
+
self.qk_norm = bool(qk_norm)
|
| 62 |
+
if self.qk_norm:
|
| 63 |
+
self.q_ln = nnx.RMSNorm(self.head_dim, rngs=rngs)
|
| 64 |
+
self.k_ln = nnx.RMSNorm(self.head_dim, rngs=rngs)
|
| 65 |
+
# Learnable per-head gain on the attention logits (same fix already used for the injection).
|
| 66 |
+
# With random-init queries the q.k logits are ~0, so softmax over 432 patches is near-uniform;
|
| 67 |
+
# that (a) makes every query read the SAME mean(V) and (b) starves dL/dQ,K (Jacobian ~1/432)
|
| 68 |
+
# so the queries never train. exp(log_gain) with init 32 sharpens attention at init, which
|
| 69 |
+
# both diversifies the per-query reads and unfreezes the Q/K gradients.
|
| 70 |
+
self.logit_gain = bool(logit_gain)
|
| 71 |
+
if self.logit_gain:
|
| 72 |
+
# CLAMPED: exp(log_gain) is unbounded, and this param sits in the high-LR 'core' group.
|
| 73 |
+
# Unclamped, a few large updates make exp(log_gain) blow up -> logits overflow -> NaN
|
| 74 |
+
# (observed: gain 32 already gives max|logit| ~168 vs ~5 baseline). jnp.clip also zeroes
|
| 75 |
+
# the gradient outside the range, so the parameter self-arrests instead of running away.
|
| 76 |
+
self.log_gain = nnx.Param(jnp.full((num_heads,), jnp.log(jnp.asarray(logit_gain_init, jnp.float32))))
|
| 77 |
+
# plain Python math (NOT jnp): __init__ runs under jit tracing, so float(jnp...)
|
| 78 |
+
# raises ConcretizationTypeError. This is a static constant, no tracing needed.
|
| 79 |
+
self.log_gain_max = _math.log(max(float(logit_gain_max), 1.0))
|
| 80 |
+
|
| 81 |
+
def __call__(self, q, kv, key_pad_mask=None, kv_addr=None, attn_bias=None):
|
| 82 |
+
# q:[b,Lq,d] kv:[b,Lk,d] key_pad_mask:[b,Lk] True=pad (ignored)
|
| 83 |
+
# kv_addr:[b|1,Lk,d] optional ADDRESS stream (K/V split): added to the keys ONLY, so it steers
|
| 84 |
+
# routing (which tokens each query reads) but is structurally excluded from the values -- an
|
| 85 |
+
# input-independent address can never leak into the output and dilute per-sample content.
|
| 86 |
+
# attn_bias:[h,Lq,Lk] (broadcast over batch) additive logit bias, e.g. a locality prior.
|
| 87 |
+
h = self.num_heads
|
| 88 |
+
Q = einops.rearrange(self.q_proj(q), "b l (h d) -> b h l d", h=h)
|
| 89 |
+
k_in = kv if kv_addr is None else kv + kv_addr
|
| 90 |
+
K = einops.rearrange(self.k_proj(k_in), "b l (h d) -> b h l d", h=h)
|
| 91 |
+
V = einops.rearrange(self.v_proj(kv), "b l (h d) -> b h l d", h=h)
|
| 92 |
+
if self.qk_norm: # bounds |q.k| structurally; see __init__ for the saturation evidence
|
| 93 |
+
Q = self.q_ln(Q)
|
| 94 |
+
K = self.k_ln(K)
|
| 95 |
+
logits = jnp.einsum("bhqd,bhkd->bhqk", Q, K) * (self.head_dim**-0.5)
|
| 96 |
+
if self.logit_gain:
|
| 97 |
+
g = jnp.clip(self.log_gain.value, -self.log_gain_max, self.log_gain_max)
|
| 98 |
+
logits = logits * jnp.exp(g)[None, :, None, None].astype(logits.dtype)
|
| 99 |
+
if attn_bias is not None:
|
| 100 |
+
logits = logits + attn_bias[None].astype(logits.dtype) # [1,h,Lq,Lk] broadcast over batch
|
| 101 |
+
if key_pad_mask is not None:
|
| 102 |
+
logits = jnp.where(key_pad_mask[:, None, None, :], jnp.asarray(-1e30, logits.dtype), logits)
|
| 103 |
+
probs = jax.nn.softmax(logits.astype(jnp.float32), axis=-1).astype(logits.dtype)
|
| 104 |
+
ctx = jnp.einsum("bhqk,bhkd->bhqd", probs, V)
|
| 105 |
+
ctx = einops.rearrange(ctx, "b h q d -> b q (h d)")
|
| 106 |
+
return self.out_proj(ctx)
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class ResidualCrossAttn(nnx.Module):
|
| 110 |
+
"""Pre-LN residual cross-attention: out = q_hidden + scale * MHA(LN_q(q_hidden), LN_kv(kv))."""
|
| 111 |
+
|
| 112 |
+
def __init__(self, dim: int, num_heads: int, *, logit_gain: bool = False,
|
| 113 |
+
logit_gain_init: float = 32.0, logit_gain_max: float = 16.0,
|
| 114 |
+
norm_attn_out: bool = False, qk_norm: bool = False, rngs: nnx.Rngs):
|
| 115 |
+
self.q_norm = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs)
|
| 116 |
+
self.kv_norm = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs)
|
| 117 |
+
self.attn = MHACrossAttn(dim, num_heads, logit_gain=logit_gain,
|
| 118 |
+
logit_gain_init=logit_gain_init, logit_gain_max=logit_gain_max,
|
| 119 |
+
qk_norm=qk_norm, rngs=rngs)
|
| 120 |
+
# The residual adds the RAW query. If ||attn_out|| >> ||q|| (measured ~500 vs ~1.6, i.e. 300:1)
|
| 121 |
+
# the shared attention output swamps per-query identity and every output collapses to
|
| 122 |
+
# mlp(q_i + const) with cos ~ 1.0. Normalizing the attention output before the residual puts
|
| 123 |
+
# the two terms on comparable scale, preserving query identity even if attention stays uniform.
|
| 124 |
+
self.out_norm = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs) if norm_attn_out else None
|
| 125 |
+
|
| 126 |
+
def __call__(self, q_hidden, kv_hidden, key_pad_mask=None, residual_scale: float = 1.0, kv_addr=None,
|
| 127 |
+
attn_bias=None):
|
| 128 |
+
q = self.q_norm(q_hidden)
|
| 129 |
+
kv = self.kv_norm(kv_hidden)
|
| 130 |
+
# kv_addr bypasses kv_norm deliberately: the payload is normalized for stable value scale,
|
| 131 |
+
# while the address keeps its own (MLP-output) scale as a routing bias on the keys.
|
| 132 |
+
out = self.attn(q, kv, key_pad_mask=key_pad_mask, kv_addr=kv_addr, attn_bias=attn_bias)
|
| 133 |
+
if self.out_norm is not None:
|
| 134 |
+
out = self.out_norm(out)
|
| 135 |
+
return q_hidden + residual_scale * out
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
class ResidualMlp(nnx.Module):
|
| 139 |
+
"""Pre-LN residual MLP: x + Linear2(gelu(Linear1(LN(x))))."""
|
| 140 |
+
|
| 141 |
+
def __init__(self, dim: int, mlp_ratio: float, *, rngs: nnx.Rngs):
|
| 142 |
+
hidden = int(dim * mlp_ratio)
|
| 143 |
+
self.norm = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs)
|
| 144 |
+
self.fc1 = nnx.Linear(dim, hidden, rngs=rngs)
|
| 145 |
+
self.fc2 = nnx.Linear(hidden, dim, rngs=rngs)
|
| 146 |
+
|
| 147 |
+
def __call__(self, x):
|
| 148 |
+
return x + self.fc2(_gelu(self.fc1(self.norm(x))))
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
class ProjLN(nnx.Module):
|
| 152 |
+
"""Linear(in->H) -> gelu -> Linear(H->H) -> LayerNorm(H). Used for layer projectors & t5_projector."""
|
| 153 |
+
|
| 154 |
+
def __init__(self, in_dim: int, dim: int, *, rngs: nnx.Rngs):
|
| 155 |
+
self.fc1 = nnx.Linear(in_dim, dim, rngs=rngs)
|
| 156 |
+
self.fc2 = nnx.Linear(dim, dim, rngs=rngs)
|
| 157 |
+
self.norm = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs)
|
| 158 |
+
|
| 159 |
+
def __call__(self, x):
|
| 160 |
+
return self.norm(self.fc2(_gelu(self.fc1(x))))
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
class Mlp2(nnx.Module):
|
| 164 |
+
"""Linear(in->hidden) -> gelu -> Linear(hidden->out). Used for ray_mlp & pos2d_mlp (no LN)."""
|
| 165 |
+
|
| 166 |
+
def __init__(self, in_dim: int, hidden: int, out_dim: int, *, rngs: nnx.Rngs):
|
| 167 |
+
self.fc1 = nnx.Linear(in_dim, hidden, rngs=rngs)
|
| 168 |
+
self.fc2 = nnx.Linear(hidden, out_dim, rngs=rngs)
|
| 169 |
+
|
| 170 |
+
def __call__(self, x):
|
| 171 |
+
return self.fc2(_gelu(self.fc1(x)))
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def _locality_dist2(num_queries, h, w):
|
| 175 |
+
"""Squared distance [K, h*w] between each query's tiled anchor and each patch position, both in
|
| 176 |
+
a normalized [0,1]^2 grid (patches row-major to match _fuse_layers 'b (h w)')."""
|
| 177 |
+
import numpy as _np
|
| 178 |
+
ys, xs = _np.meshgrid(_np.linspace(0.0, 1.0, h), _np.linspace(0.0, 1.0, w), indexing="ij")
|
| 179 |
+
patch = _np.stack([ys.ravel(), xs.ravel()], axis=-1) # [h*w, 2]
|
| 180 |
+
ar = int(_np.ceil(_np.sqrt(num_queries))); ac = int(_np.ceil(num_queries / ar))
|
| 181 |
+
ay, ax = _np.meshgrid(_np.linspace(0.0, 1.0, ar), _np.linspace(0.0, 1.0, ac), indexing="ij")
|
| 182 |
+
anch = _np.stack([ay.ravel(), ax.ravel()], axis=-1)[:num_queries] # [K, 2]
|
| 183 |
+
return (((anch[:, None, :] - patch[None, :, :]) ** 2).sum(-1)).astype(_np.float32) # [K, h*w]
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
class PerceiverDownsampler(nnx.Module):
|
| 187 |
+
"""432 grid tokens -> K learned-query tokens (single cross-attn + residual MLP)."""
|
| 188 |
+
|
| 189 |
+
def __init__(self, dim: int, num_queries: int, num_heads: int, *, query_std: float = 0.02,
|
| 190 |
+
logit_gain: bool = False, logit_gain_init: float = 32.0, logit_gain_max: float = 16.0,
|
| 191 |
+
norm_attn_out: bool = False, qk_norm: bool = False, norm_out: bool = False,
|
| 192 |
+
locality: bool = False, grid_hw: tuple = (18, 24), locality_gamma_init: float = 4.0,
|
| 193 |
+
rngs: nnx.Rngs):
|
| 194 |
+
key = rngs.params()
|
| 195 |
+
self.query = nnx.Param(jax.random.normal(key, (1, num_queries, dim)) * query_std)
|
| 196 |
+
self.xattn = ResidualCrossAttn(dim, num_heads, logit_gain=logit_gain,
|
| 197 |
+
logit_gain_init=logit_gain_init, logit_gain_max=logit_gain_max,
|
| 198 |
+
norm_attn_out=norm_attn_out, qk_norm=qk_norm, rngs=rngs)
|
| 199 |
+
self.mlp = ResidualMlp(dim, mlp_ratio=2.0, rngs=rngs)
|
| 200 |
+
# LOCALITY: each query gets a fixed anchor tiling the grid; a learnable per-head gamma biases
|
| 201 |
+
# the attention logits by -gamma*dist2 so each of the K tokens preferentially reads its own
|
| 202 |
+
# neighborhood (a local descriptor) instead of a global average -- fixes over-averaging while
|
| 203 |
+
# staying flexible (gamma can shrink toward global if content demands).
|
| 204 |
+
self.locality = bool(locality)
|
| 205 |
+
if self.locality:
|
| 206 |
+
self._loc_nq = int(num_queries) # ints only (nnx rejects bare array attrs);
|
| 207 |
+
self._loc_gh = (int(grid_hw[0]), int(grid_hw[1])) # dist2 is recomputed (static) in __call__
|
| 208 |
+
self.loc_log_gamma = nnx.Param(jnp.full((num_heads,), _math.log(max(locality_gamma_init, 1e-3))))
|
| 209 |
+
# FIX 4: bound the perceiver output. Measured without it: the residual MLP amplified a
|
| 210 |
+
# unit-rms input to rms 1790 (x1900). Nothing penalized that -- the injection's kv_norm makes
|
| 211 |
+
# downstream scale irrelevant and weight_decay was 1e-10 -- so the block became an
|
| 212 |
+
# unconstrained amplifier whose output was ~92% batch-constant.
|
| 213 |
+
self.out_ln = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs) if norm_out else None
|
| 214 |
+
|
| 215 |
+
def __call__(self, tokens, addr=None, token_embed=None):
|
| 216 |
+
# tokens = PAYLOAD (per-sample content: DA3 latents + depth enc). addr = optional ADDRESS
|
| 217 |
+
# stream (pos/ray/view annotations) -> keys only; see MHACrossAttn.kv_addr.
|
| 218 |
+
# token_embed [1,K,H]: per-output-token identity added to the QUERY (not the final bank). This
|
| 219 |
+
# shapes WHICH patches each of the K queries reads, so it produces per-token-distinct AND
|
| 220 |
+
# per-sample-varying output -- unlike a post-hoc constant it survives bank-centering.
|
| 221 |
+
b = tokens.shape[0]
|
| 222 |
+
q = jnp.broadcast_to(self.query.value, (b, *self.query.value.shape[1:]))
|
| 223 |
+
if token_embed is not None:
|
| 224 |
+
q = q + token_embed
|
| 225 |
+
bias = None
|
| 226 |
+
if self.locality:
|
| 227 |
+
gamma = jnp.exp(self.loc_log_gamma.value) # [h] >0
|
| 228 |
+
dist2 = jnp.asarray(_locality_dist2(self._loc_nq, *self._loc_gh)) # static const [K, Lk]
|
| 229 |
+
bias = -gamma[:, None, None] * dist2[None] # [h, K, Lk]
|
| 230 |
+
z = self.xattn(q, tokens, residual_scale=1.0, kv_addr=addr, attn_bias=bias) # residual adds RAW q
|
| 231 |
+
out = self.mlp(z)
|
| 232 |
+
return self.out_ln(out) if self.out_ln is not None else out
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
class LanguageFusionStack(nnx.Module):
|
| 236 |
+
"""N x [cross-attn(bank, lang) + residual-MLP], with language padding mask."""
|
| 237 |
+
|
| 238 |
+
def __init__(self, dim: int, depth: int, num_heads: int, *, qk_norm: bool = False,
|
| 239 |
+
rngs: nnx.Rngs):
|
| 240 |
+
self.layers = [
|
| 241 |
+
(ResidualCrossAttn(dim, num_heads, qk_norm=qk_norm, rngs=rngs),
|
| 242 |
+
ResidualMlp(dim, mlp_ratio=4.0, rngs=rngs))
|
| 243 |
+
for _ in range(depth)
|
| 244 |
+
]
|
| 245 |
+
|
| 246 |
+
def __call__(self, geo, lang_tokens, lang_pad_mask):
|
| 247 |
+
for xattn, mlp in self.layers:
|
| 248 |
+
geo = xattn(geo, lang_tokens, key_pad_mask=lang_pad_mask, residual_scale=1.0)
|
| 249 |
+
geo = mlp(geo)
|
| 250 |
+
return geo
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def _cam_pose_feat(ext):
|
| 254 |
+
"""Camera-pose feature [b,12] from a w2c extrinsic [b,4,4]: R_c2w flattened (9) + camera center (3).
|
| 255 |
+
Gives the cross-view fusion the RELATIVE viewpoints so it can reason across cameras geometrically."""
|
| 256 |
+
R = ext[:, :3, :3] # R_w2c
|
| 257 |
+
t = ext[:, :3, 3]
|
| 258 |
+
Rc2w = jnp.swapaxes(R, -1, -2)
|
| 259 |
+
center = -jnp.einsum("bij,bj->bi", Rc2w, t) # camera center in world
|
| 260 |
+
return jnp.concatenate([Rc2w.reshape(ext.shape[0], 9), center], axis=-1).astype(jnp.float32)
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
class CrossViewFusion(nnx.Module):
|
| 264 |
+
"""Self-attention over the CONCATENATED per-view tokens so the three views exchange 3D information
|
| 265 |
+
(grounded by per-view camera pose), turning three separate 2.5D banks into one integrated scene."""
|
| 266 |
+
|
| 267 |
+
def __init__(self, dim: int, num_heads: int, depth: int, *, qk_norm: bool = False, rngs: nnx.Rngs):
|
| 268 |
+
self.blocks = [
|
| 269 |
+
(ResidualCrossAttn(dim, num_heads, qk_norm=qk_norm, rngs=rngs),
|
| 270 |
+
ResidualMlp(dim, mlp_ratio=4.0, rngs=rngs))
|
| 271 |
+
for _ in range(depth)
|
| 272 |
+
]
|
| 273 |
+
|
| 274 |
+
def __call__(self, x): # x [b, N_total, H]
|
| 275 |
+
for attn, mlp in self.blocks:
|
| 276 |
+
x = attn(x, x, residual_scale=1.0) # self-attention (q == kv)
|
| 277 |
+
x = mlp(x)
|
| 278 |
+
return x
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
# ---------------------------------------------------------------------------
|
| 282 |
+
# geometry helpers
|
| 283 |
+
# ---------------------------------------------------------------------------
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def compute_world_ray_6d(ray_local, ext_w2c):
|
| 287 |
+
"""ray_local [b,3,h,w] cam-local unit dir; ext_w2c [b,4,4] OpenCV world->cam.
|
| 288 |
+
|
| 289 |
+
Returns [b,6,h,w] = concat([origin_world(camera center), dir_world]).
|
| 290 |
+
"""
|
| 291 |
+
R_w2c = ext_w2c[:, :3, :3] # [b,3,3]
|
| 292 |
+
t_w2c = ext_w2c[:, :3, 3] # [b,3]
|
| 293 |
+
R_c2w = jnp.swapaxes(R_w2c, -1, -2)
|
| 294 |
+
pos_world = -jnp.einsum("bij,bj->bi", R_c2w, t_w2c) # [b,3] camera center in world
|
| 295 |
+
b, _, h, w = ray_local.shape
|
| 296 |
+
dir_world = jnp.einsum("bij,bjk->bik", R_c2w, ray_local.reshape(b, 3, h * w)).reshape(b, 3, h, w)
|
| 297 |
+
origin = jnp.broadcast_to(pos_world[:, :, None, None], (b, 3, h, w))
|
| 298 |
+
return jnp.concatenate([origin, dir_world], axis=1) # [b,6,h,w]
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def _grid_coords(h: int, w: int):
|
| 302 |
+
v = 2.0 * jnp.arange(h) / (h - 1) - 1.0
|
| 303 |
+
u = 2.0 * jnp.arange(w) / (w - 1) - 1.0
|
| 304 |
+
yy, xx = jnp.meshgrid(v, u, indexing="ij")
|
| 305 |
+
return jnp.stack([xx, yy], axis=-1).reshape(1, h * w, 2) # [1,432,2] (x=u, y=v), row-major
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
# ---------------------------------------------------------------------------
|
| 309 |
+
# bank builder
|
| 310 |
+
# ---------------------------------------------------------------------------
|
| 311 |
+
|
| 312 |
+
_VIEWS = (("main", 0, 128), ("left", 1, 96), ("right", 2, 96))
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
class SpatialBankBuilder(nnx.Module):
|
| 316 |
+
"""Cached DA3 (feats/ray/depth) + extrinsics + ModernBERT feats -> 3 per-view banks."""
|
| 317 |
+
|
| 318 |
+
def __init__(
|
| 319 |
+
self,
|
| 320 |
+
*,
|
| 321 |
+
hidden_dim: int = 1024,
|
| 322 |
+
da3_channels: int = 1536,
|
| 323 |
+
num_layers: int = 4,
|
| 324 |
+
grid_hw: tuple[int, int] = (18, 24),
|
| 325 |
+
lang_dim: int = 1024, # ModernBERT-large last_hidden width (768) -> set by config
|
| 326 |
+
num_heads: int = 8,
|
| 327 |
+
lang_fusion_depth: int = 2,
|
| 328 |
+
perceiver_query_std: float = 0.02,
|
| 329 |
+
qk_norm: bool = False,
|
| 330 |
+
perceiver_norm_out: bool = False,
|
| 331 |
+
pos_emb_scale: float = 1.0,
|
| 332 |
+
perceiver_logit_gain: bool = False,
|
| 333 |
+
perceiver_logit_gain_init: float = 32.0,
|
| 334 |
+
perceiver_logit_gain_max: float = 16.0,
|
| 335 |
+
perceiver_norm_attn_out: bool = False,
|
| 336 |
+
bank_token_embed: bool = False,
|
| 337 |
+
bank_center: bool = False,
|
| 338 |
+
aux_geom_head: bool = False,
|
| 339 |
+
depth_target_only: bool = False,
|
| 340 |
+
kv_split: bool = False,
|
| 341 |
+
depth_dropout: float = 0.0,
|
| 342 |
+
perc_locality: bool = False,
|
| 343 |
+
cross_view: bool = False,
|
| 344 |
+
cross_view_depth: int = 2,
|
| 345 |
+
bank_token_embed_query: bool = True,
|
| 346 |
+
use_depth_conf: bool = False,
|
| 347 |
+
use_pose_enc: bool = False,
|
| 348 |
+
use_cam_tokens: bool = False,
|
| 349 |
+
cam_token_dim: int = 2048,
|
| 350 |
+
pose_enc_dim: int = 9,
|
| 351 |
+
feat_input_norm: bool = False,
|
| 352 |
+
rngs: nnx.Rngs,
|
| 353 |
+
):
|
| 354 |
+
# placement of bank_token_embeds: True (new) = added to the perceiver QUERY (center-surviving);
|
| 355 |
+
# False (old) = added POST-fusion (dead under bank_center). Set False to faithfully evaluate
|
| 356 |
+
# checkpoints trained before the move (e.g. spatretrain/strongbase/kvsplit_desk).
|
| 357 |
+
self._bte_query = bool(bank_token_embed_query)
|
| 358 |
+
# K/V SPLIT (2026-07-23): separate ADDRESS from PAYLOAD instead of one additive sum.
|
| 359 |
+
# payload (values) = DA3 latents + depth encoding -- what flows into the bank
|
| 360 |
+
# address (keys) = pos_emb + ray_emb + view_emb -- where it is; routing only
|
| 361 |
+
# In the summed design the constant "where" terms enter the value stream and, under broad
|
| 362 |
+
# attention, average into an input-independent constant (the measured collapse). With the
|
| 363 |
+
# split, addresses are structurally excluded from the output: constants can route, but only
|
| 364 |
+
# per-sample content can flow. depth moves to the payload (per-sample geometry content);
|
| 365 |
+
# the Plucker ray (camera geometry) stays as address.
|
| 366 |
+
self.kv_split = bool(kv_split)
|
| 367 |
+
# depth_dropout: during training, zero the depth encoding for this fraction of samples so the
|
| 368 |
+
# bank cannot rely on the explicit depth channel alone -- the DA3 features must carry the
|
| 369 |
+
# geometry too. Applied only when a dropout rng is passed (training); inference keeps depth.
|
| 370 |
+
self.depth_dropout = float(depth_dropout)
|
| 371 |
+
self.bank_center = bool(bank_center)
|
| 372 |
+
# AUX GEOMETRY HEAD (2026-07-23): decode the PERCEIVER token output back to per-patch log-depth
|
| 373 |
+
# (grid-position queries cross-attend to the K perceiver tokens). Supervised by the DA3 depth we
|
| 374 |
+
# already have (ray_flat[...,6]), this FORCES the perceiver output to carry per-sample scene
|
| 375 |
+
# geometry regardless of whether the action loss rewards it -- the guaranteed fix for the
|
| 376 |
+
# "geometry read but unused" verdict. Shared across views; queries are the (constant) grid
|
| 377 |
+
# positions so the prediction varies only through the per-sample perceiver tokens.
|
| 378 |
+
self.aux_geom_head = bool(aux_geom_head)
|
| 379 |
+
# depth TARGET-ONLY mode: zero the log-depth channel in the ray7 INPUT so depth is never given
|
| 380 |
+
# to the network -- only used as the aux target. Without this the aux task is circular (depth
|
| 381 |
+
# in -> depth out = a trivial autoencoder through the perceiver bottleneck, satisfiable without
|
| 382 |
+
# reading the DA3 features at all). With it, the ONLY path to the target is extracting depth
|
| 383 |
+
# from the DA3 features -> the aux loss forces genuine feature use. Plucker ray dirs (ch 0-5)
|
| 384 |
+
# remain as input: they are camera geometry, not the answer.
|
| 385 |
+
self.depth_target_only = bool(depth_target_only)
|
| 386 |
+
H = hidden_dim
|
| 387 |
+
self.hidden_dim = H
|
| 388 |
+
self.num_layers = num_layers
|
| 389 |
+
self.grid_hw = grid_hw
|
| 390 |
+
# (a) per-tap projectors + layer embed + fuse
|
| 391 |
+
self.layer_projectors = [ProjLN(da3_channels, H, rngs=rngs) for _ in range(num_layers)]
|
| 392 |
+
self.layer_embed = nnx.Param(jax.random.normal(rngs.params(), (num_layers, H)) * 0.02)
|
| 393 |
+
self.layer_fuse = nnx.Linear(num_layers * H, H, rngs=rngs)
|
| 394 |
+
# (b) ray encoder. kv_split: Plucker-6 only (address) + separate depth encoder (payload).
|
| 395 |
+
# legacy: scale-aware ray (Plucker-6 + log-depth = 7) summed into everything.
|
| 396 |
+
if self.kv_split:
|
| 397 |
+
self.ray_mlp = Mlp2(6, 256, H, rngs=rngs)
|
| 398 |
+
self.depth_mlp = Mlp2(1, 256, H, rngs=rngs)
|
| 399 |
+
else:
|
| 400 |
+
self.ray_mlp = Mlp2(7, 256, H, rngs=rngs)
|
| 401 |
+
# (c) 2D grid pos + per-view embedding
|
| 402 |
+
self.pos2d_mlp = Mlp2(2, 256, H, rngs=rngs)
|
| 403 |
+
self.view_embed = nnx.Embed(3, H, rngs=rngs)
|
| 404 |
+
# (d) language projector (ModernBERT feat -> H)
|
| 405 |
+
self.t5_projector = ProjLN(lang_dim, H, rngs=rngs)
|
| 406 |
+
# FIX 5: pos_emb is INPUT-INDEPENDENT and was measured at rms 5.03 vs the DA3-derived
|
| 407 |
+
# signal's 4.38 -- the constant was LARGER than the content it annotates, diluting
|
| 408 |
+
# per-sample diversity 0.474 -> 0.270 before the perceiver even ran. Scale it down so
|
| 409 |
+
# position annotates content instead of dominating it.
|
| 410 |
+
self.pos_emb_scale = float(pos_emb_scale)
|
| 411 |
+
# (e) per-view perceiver + language fusion. perc_locality anchors each query to a grid region.
|
| 412 |
+
self.perceivers = {
|
| 413 |
+
name: PerceiverDownsampler(H, k, num_heads, query_std=perceiver_query_std,
|
| 414 |
+
logit_gain=perceiver_logit_gain,
|
| 415 |
+
logit_gain_init=perceiver_logit_gain_init,
|
| 416 |
+
logit_gain_max=perceiver_logit_gain_max,
|
| 417 |
+
norm_attn_out=perceiver_norm_attn_out,
|
| 418 |
+
qk_norm=qk_norm, norm_out=perceiver_norm_out,
|
| 419 |
+
locality=perc_locality, grid_hw=grid_hw, rngs=rngs)
|
| 420 |
+
for name, _, k in _VIEWS
|
| 421 |
+
}
|
| 422 |
+
self.lang_fusers = {name: LanguageFusionStack(H, lang_fusion_depth, num_heads,
|
| 423 |
+
qk_norm=qk_norm, rngs=rngs)
|
| 424 |
+
for name, _, _ in _VIEWS}
|
| 425 |
+
# (e2) CROSS-VIEW 3D FUSION: after the per-view perceivers, add a camera-pose embed to each
|
| 426 |
+
# view's tokens, concatenate, and self-attend so views exchange 3D info; then split back.
|
| 427 |
+
self.cross_view = bool(cross_view)
|
| 428 |
+
if self.cross_view:
|
| 429 |
+
self.cam_pose_mlp = Mlp2(12, 256, H, rngs=rngs)
|
| 430 |
+
self.cross_view_fusion = CrossViewFusion(H, num_heads, cross_view_depth, qk_norm=qk_norm, rngs=rngs)
|
| 431 |
+
# --- VGGT-Omega enrichments (all gated; DA3 path leaves them off) ---
|
| 432 |
+
# depth_conf: VGGT per-patch confidence -> a payload reliability channel (added to the values,
|
| 433 |
+
# so the bank can down-weight geometry where VGGT is uncertain).
|
| 434 |
+
self.use_depth_conf = bool(use_depth_conf)
|
| 435 |
+
if self.use_depth_conf:
|
| 436 |
+
self.conf_mlp = Mlp2(1, 256, H, rngs=rngs)
|
| 437 |
+
# pose_enc: VGGT learned camera encoding (trans+quat+fov) -> added to the cross-view camera
|
| 438 |
+
# feature (a learned pose signal alongside the hand-built R|t feature).
|
| 439 |
+
self.use_pose_enc = bool(use_pose_enc)
|
| 440 |
+
if self.use_pose_enc:
|
| 441 |
+
self.pose_enc_mlp = Mlp2(pose_enc_dim, 256, H, rngs=rngs)
|
| 442 |
+
# cam_tokens: VGGT camera+register global tokens -> projected and APPENDED to each view's final
|
| 443 |
+
# bank (global scene/camera context the action expert can attend to). Appended after fusion so
|
| 444 |
+
# they never disturb the perceiver locality grid or the cross-view token split.
|
| 445 |
+
self.use_cam_tokens = bool(use_cam_tokens)
|
| 446 |
+
if self.use_cam_tokens:
|
| 447 |
+
# VGGT camera/register tokens carry ViT massive-activation outliers (absmax ~180); LayerNorm
|
| 448 |
+
# the raw tokens BEFORE the projector so the projector weight-grads stay O(1) (else runaway).
|
| 449 |
+
self.cam_in_norm = nnx.LayerNorm(cam_token_dim, epsilon=1e-5, rngs=rngs)
|
| 450 |
+
self.cam_token_proj = ProjLN(cam_token_dim, H, rngs=rngs)
|
| 451 |
+
# feat_input_norm: LayerNorm the raw backbone features before the layer projectors. DA3-GIANT
|
| 452 |
+
# features are O(1) so this was unneeded; VGGT aggregator taps have outlier channels (absmax ~160)
|
| 453 |
+
# that blow up the projector weight-grads (grad_norm 62 vs DA3's 0.77 -> NaN by step ~50).
|
| 454 |
+
self.feat_input_norm = bool(feat_input_norm)
|
| 455 |
+
if self.feat_input_norm:
|
| 456 |
+
self.feat_in_norm = nnx.LayerNorm(da3_channels, epsilon=1e-5, rngs=rngs)
|
| 457 |
+
# (f) v2: learned per-token embedding added to each view's FINAL bank tokens. Guarantees
|
| 458 |
+
# persistent cross-token diversity — the quantity that drives softmax gradients to the
|
| 459 |
+
# injection's Q/K (shared content cancels in the softmax jacobian, so without this the
|
| 460 |
+
# attention pattern barely trains; measured ~1000x slower than V/out in v1).
|
| 461 |
+
self.bank_token_embeds = (
|
| 462 |
+
{name: nnx.Param(jax.random.normal(rngs.params(), (1, k, H)) * 0.05) for name, _, k in _VIEWS}
|
| 463 |
+
if bank_token_embed
|
| 464 |
+
else None
|
| 465 |
+
)
|
| 466 |
+
# aux geometry decoder (shared across views): grid-pos query -> attend perceiver tokens -> log-depth
|
| 467 |
+
if self.aux_geom_head:
|
| 468 |
+
self.aux_q = nnx.Linear(H, H, rngs=rngs)
|
| 469 |
+
self.aux_k = nnx.Linear(H, H, rngs=rngs)
|
| 470 |
+
self.aux_v = nnx.Linear(H, H, rngs=rngs)
|
| 471 |
+
self.aux_out = nnx.Linear(H, 1, rngs=rngs)
|
| 472 |
+
|
| 473 |
+
def _fuse_layers(self, feats_v):
|
| 474 |
+
# feats_v: [b, num_layers, C, h, w] -> [b, 432, H]
|
| 475 |
+
b, L, C, h, w = feats_v.shape
|
| 476 |
+
parts = []
|
| 477 |
+
for li in range(self.num_layers):
|
| 478 |
+
flat = einops.rearrange(feats_v[:, li], "b c h w -> b (h w) c") # row-major
|
| 479 |
+
if self.feat_input_norm:
|
| 480 |
+
flat = self.feat_in_norm(flat) # tame VGGT outlier channels before projection
|
| 481 |
+
p = self.layer_projectors[li](flat) + self.layer_embed.value[li][None, None, :]
|
| 482 |
+
parts.append(p)
|
| 483 |
+
return self.layer_fuse(jnp.concatenate(parts, axis=-1))
|
| 484 |
+
|
| 485 |
+
def _ray7(self, ray_v, depth_v, ext_v):
|
| 486 |
+
# ray_v [b,3,h,w], depth_v [b,1,h,w], ext_v [b,4,4] -> [b,432,7]
|
| 487 |
+
ray6 = compute_world_ray_6d(ray_v, ext_v) # [b,6,h,w]
|
| 488 |
+
logd = jnp.log(jnp.clip(depth_v.astype(jnp.float32), a_min=1e-3)).astype(ray6.dtype) # [b,1,h,w]
|
| 489 |
+
ray7 = jnp.concatenate([ray6, logd], axis=1) # [b,7,h,w]
|
| 490 |
+
return einops.rearrange(ray7, "b c h w -> b (h w) c")
|
| 491 |
+
|
| 492 |
+
def __call__(self, feats, ray, depth, extrinsics, lang_feat, lang_mask, return_aux: bool = False,
|
| 493 |
+
depth_drop_rng=None, depth_conf=None, pose_enc=None, cam_tokens=None):
|
| 494 |
+
# feats [b,L,V,C,h,w]; ray [b,V,3,h,w]; depth [b,V,1,h,w]; extrinsics [b,V,4,4]
|
| 495 |
+
# lang_feat [b,Lt,lang_dim]; lang_mask [b,Lt] True=real token
|
| 496 |
+
# return_aux: also return the aux geometry (log-depth reconstruction) loss (training only).
|
| 497 |
+
# depth_drop_rng: training-only rng enabling depth_dropout (kv_split path); None = keep depth.
|
| 498 |
+
h, w = self.grid_hw
|
| 499 |
+
pos_emb = self.pos2d_mlp(_grid_coords(h, w).astype(feats.dtype)) # [1,432,H]
|
| 500 |
+
if self.pos_emb_scale != 1.0:
|
| 501 |
+
pos_emb = pos_emb * jnp.asarray(self.pos_emb_scale, pos_emb.dtype)
|
| 502 |
+
lang_tokens = self.t5_projector(lang_feat) # [b,Lt,H]
|
| 503 |
+
lang_pad = jnp.logical_not(lang_mask) # True=pad
|
| 504 |
+
geos = {}
|
| 505 |
+
aux_losses = []
|
| 506 |
+
for name, vidx, _k in _VIEWS:
|
| 507 |
+
fused = self._fuse_layers(feats[:, :, vidx]) # [b,432,H]
|
| 508 |
+
ray_flat = self._ray7(ray[:, vidx], depth[:, vidx], extrinsics[:, vidx]) # [b,432,7]
|
| 509 |
+
view_emb = self.view_embed(jnp.asarray(vidx))[None, None, :] # [1,1,H]
|
| 510 |
+
_bte = self.bank_token_embeds[name].value if self.bank_token_embeds is not None else None
|
| 511 |
+
tok_emb = _bte if self._bte_query else None # into query (new) vs post-fusion (old)
|
| 512 |
+
if self.kv_split:
|
| 513 |
+
# K/V split: payload (values) = DA3 latents + depth enc; address (keys) = pos/ray/view.
|
| 514 |
+
ray_emb = self.ray_mlp(ray_flat[..., :6].astype(feats.dtype)) # Plucker only [b,432,H]
|
| 515 |
+
depth_emb = self.depth_mlp(ray_flat[..., 6:7].astype(feats.dtype)) # [b,432,H]
|
| 516 |
+
if depth_drop_rng is not None and self.depth_dropout > 0.0:
|
| 517 |
+
# per-sample: this fraction of the batch sees NO explicit depth channel, so the
|
| 518 |
+
# DA3 features must carry the geometry for those samples (redundancy pressure).
|
| 519 |
+
keep = jax.random.bernoulli(
|
| 520 |
+
jax.random.fold_in(depth_drop_rng, vidx),
|
| 521 |
+
1.0 - self.depth_dropout, (depth_emb.shape[0], 1, 1),
|
| 522 |
+
)
|
| 523 |
+
depth_emb = depth_emb * keep.astype(depth_emb.dtype)
|
| 524 |
+
payload = fused + depth_emb # [b,432,H]
|
| 525 |
+
if self.use_depth_conf and depth_conf is not None:
|
| 526 |
+
conf_flat = einops.rearrange(depth_conf[:, vidx], "b c h w -> b (h w) c") # [b,432,1]
|
| 527 |
+
conf_flat = jnp.log(jnp.clip(conf_flat.astype(feats.dtype), 1e-3)) # bound VGGT's exp-scaled conf
|
| 528 |
+
payload = payload + self.conf_mlp(conf_flat)
|
| 529 |
+
addr = view_emb + pos_emb + ray_emb # routing-only annotations
|
| 530 |
+
geo = self.perceivers[name](payload, addr=addr, token_embed=tok_emb) # [b,K,H]
|
| 531 |
+
else:
|
| 532 |
+
if self.depth_target_only:
|
| 533 |
+
# depth is a TARGET, never an input: zero ch 6 (log-depth) so the aux prediction
|
| 534 |
+
# can only come from the DA3 features. Keeps ray_mlp's 7-ch shape (ckpt-compat).
|
| 535 |
+
ray_in = ray_flat.at[..., 6].set(0.0)
|
| 536 |
+
else:
|
| 537 |
+
ray_in = ray_flat
|
| 538 |
+
ray_emb = self.ray_mlp(ray_in.astype(feats.dtype)) # [b,432,H]
|
| 539 |
+
spatial = fused + view_emb + pos_emb + ray_emb # [b,432,H]
|
| 540 |
+
geo = self.perceivers[name](spatial, token_embed=tok_emb) # [b,K,H]
|
| 541 |
+
if return_aux and self.aux_geom_head:
|
| 542 |
+
# grid-pos queries (constant) attend to this view's K perceiver tokens -> per-patch
|
| 543 |
+
# log-depth. Prediction varies ONLY through geo, so a good fit REQUIRES geo to encode
|
| 544 |
+
# per-sample geometry. MSE against the true DA3 log-depth (ray_flat channel 6).
|
| 545 |
+
qh = jnp.broadcast_to(self.aux_q(pos_emb), (geo.shape[0], h * w, self.hidden_dim)) # [b,P,H]
|
| 546 |
+
kh = self.aux_k(geo) # [b,K,H]
|
| 547 |
+
vh = self.aux_v(geo) # [b,K,H]
|
| 548 |
+
scale = jnp.sqrt(jnp.asarray(self.hidden_dim, qh.dtype))
|
| 549 |
+
attn = jax.nn.softmax(jnp.einsum("bph,bkh->bpk", qh, kh) / scale, axis=-1) # [b,P,K]
|
| 550 |
+
pred_logd = self.aux_out(jnp.einsum("bpk,bkh->bph", attn, vh)) # [b,P,1]
|
| 551 |
+
true_logd = ray_flat[..., 6:7].astype(pred_logd.dtype) # [b,P,1]
|
| 552 |
+
aux_losses.append(jnp.mean(jnp.square(pred_logd - true_logd)))
|
| 553 |
+
geos[name] = geo # [b,K,H]
|
| 554 |
+
|
| 555 |
+
# ---- CROSS-VIEW 3D FUSION: views exchange info, grounded by camera pose ----
|
| 556 |
+
if self.cross_view:
|
| 557 |
+
parts = []
|
| 558 |
+
for name, vidx, _k in _VIEWS:
|
| 559 |
+
cam = self.cam_pose_mlp(_cam_pose_feat(extrinsics[:, vidx]).astype(feats.dtype)) # [b,H]
|
| 560 |
+
if self.use_pose_enc and pose_enc is not None:
|
| 561 |
+
cam = cam + self.pose_enc_mlp(pose_enc[:, vidx].astype(feats.dtype)) # learned VGGT pose
|
| 562 |
+
parts.append(geos[name] + cam[:, None, :])
|
| 563 |
+
x = self.cross_view_fusion(jnp.concatenate(parts, axis=1)) # [b, sum_k, H]
|
| 564 |
+
off = 0
|
| 565 |
+
for name, _vidx, k in _VIEWS:
|
| 566 |
+
geos[name] = x[:, off:off + k]
|
| 567 |
+
off += k
|
| 568 |
+
|
| 569 |
+
# ---- language fusion + bank-centering, per view ----
|
| 570 |
+
banks = {}
|
| 571 |
+
for name, _vidx, _k in _VIEWS:
|
| 572 |
+
bank = self.lang_fusers[name](geos[name], lang_tokens, lang_pad) # [b,K,H]
|
| 573 |
+
# bank_token_embeds: new placement shapes the perceiver query (above); OLD placement adds it
|
| 574 |
+
# here post-fusion (faithful eval of pre-move checkpoints; dead under bank_center as before).
|
| 575 |
+
if self.bank_token_embeds is not None and not self._bte_query:
|
| 576 |
+
bank = bank + self.bank_token_embeds[name].value
|
| 577 |
+
if self.bank_center:
|
| 578 |
+
# Project out the batch-mean (over the sharded batch axis => global mean under jit).
|
| 579 |
+
# A purely-constant bank now injects zero; only per-sample deviation reaches the base,
|
| 580 |
+
# so the model must use per-sample geometry or nothing. See bank_center in the config.
|
| 581 |
+
bank = bank - jnp.mean(bank, axis=0, keepdims=True)
|
| 582 |
+
if self.use_cam_tokens and cam_tokens is not None:
|
| 583 |
+
# VGGT camera+register global tokens -> projected and appended (after all fusion, so the
|
| 584 |
+
# perceiver locality grid and cross-view split are untouched). Centered for consistency.
|
| 585 |
+
ct = self.cam_token_proj(self.cam_in_norm(cam_tokens[:, _vidx].astype(feats.dtype))) # [b,17,H]
|
| 586 |
+
if self.bank_center:
|
| 587 |
+
ct = ct - jnp.mean(ct, axis=0, keepdims=True)
|
| 588 |
+
bank = jnp.concatenate([bank, ct], axis=1) # [b, K+17, H]
|
| 589 |
+
banks[name] = bank
|
| 590 |
+
if return_aux:
|
| 591 |
+
aux = jnp.mean(jnp.stack(aux_losses)) if aux_losses else jnp.asarray(0.0, jnp.float32)
|
| 592 |
+
return banks, aux
|
| 593 |
+
return banks
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/policies/b1k_policy.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""B1K Policy Transforms
|
| 2 |
+
|
| 3 |
+
Transforms BEHAVIOR-1K observations to model format.
|
| 4 |
+
|
| 5 |
+
Reference: https://github.com/wensi-ai/openpi/blob/behavior/src/openpi/policies/b1k_policy.py
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import dataclasses
|
| 9 |
+
|
| 10 |
+
import einops
|
| 11 |
+
import numpy as np
|
| 12 |
+
|
| 13 |
+
from openpi import transforms
|
| 14 |
+
from openpi.models import model as _model
|
| 15 |
+
try:
|
| 16 |
+
# 2025 path: 256-dim R1Pro proprio indices live in OmniGibson (eval stack).
|
| 17 |
+
from omnigibson.learning.utils.eval_utils import PROPRIOCEPTION_INDICES
|
| 18 |
+
except ImportError:
|
| 19 |
+
# 2026 path does its own 61-dim extraction (see b1k.training.b1k_2026) and
|
| 20 |
+
# never calls extract_state_from_proprio, so OmniGibson isn't required.
|
| 21 |
+
PROPRIOCEPTION_INDICES = None
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def make_b1k_example() -> dict:
|
| 25 |
+
"""Creates a random input example for the Droid policy."""
|
| 26 |
+
return {
|
| 27 |
+
"observation/egocentric_camera": np.random.randint(256, size=(224, 224, 3), dtype=np.uint8),
|
| 28 |
+
"observation/wrist_image_left": np.random.randint(256, size=(224, 224, 3), dtype=np.uint8),
|
| 29 |
+
"observation/wrist_image_right": np.random.randint(256, size=(224, 224, 3), dtype=np.uint8),
|
| 30 |
+
"observation/joint_position": np.random.rand(23),
|
| 31 |
+
"prompt": "do something",
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
def extract_state_from_proprio(proprio_data):
|
| 35 |
+
"""
|
| 36 |
+
We assume perfect correlation for the two gripper fingers.
|
| 37 |
+
"""
|
| 38 |
+
# extract joint position
|
| 39 |
+
base_qvel = proprio_data[..., PROPRIOCEPTION_INDICES["R1Pro"]["base_qvel"]] # 3
|
| 40 |
+
trunk_qpos = proprio_data[..., PROPRIOCEPTION_INDICES["R1Pro"]["trunk_qpos"]] # 4
|
| 41 |
+
arm_left_qpos = proprio_data[..., PROPRIOCEPTION_INDICES["R1Pro"]["arm_left_qpos"]] # 7
|
| 42 |
+
arm_right_qpos = proprio_data[..., PROPRIOCEPTION_INDICES["R1Pro"]["arm_right_qpos"]] # 7
|
| 43 |
+
|
| 44 |
+
# Extract raw gripper widths and normalize to [-1, 1] to match action space
|
| 45 |
+
left_gripper_raw = proprio_data[..., PROPRIOCEPTION_INDICES["R1Pro"]["gripper_left_qpos"]].sum(axis=-1, keepdims=True)
|
| 46 |
+
right_gripper_raw = proprio_data[..., PROPRIOCEPTION_INDICES["R1Pro"]["gripper_right_qpos"]].sum(axis=-1, keepdims=True)
|
| 47 |
+
|
| 48 |
+
# Normalize gripper widths from [0, 0.1] to [-1, 1]
|
| 49 |
+
# Based on statistics: physical range is [0, 0.1], action range is [-1, 1]
|
| 50 |
+
# Formula: normalized = 2 * (raw / max_width) - 1
|
| 51 |
+
MAX_GRIPPER_WIDTH = 0.1 # From statistics q99 values
|
| 52 |
+
left_gripper_width = 2.0 * (left_gripper_raw / MAX_GRIPPER_WIDTH) - 1.0
|
| 53 |
+
right_gripper_width = 2.0 * (right_gripper_raw / MAX_GRIPPER_WIDTH) - 1.0
|
| 54 |
+
|
| 55 |
+
# Original baseline uses incorrect order for the state
|
| 56 |
+
return np.concatenate([
|
| 57 |
+
base_qvel,
|
| 58 |
+
trunk_qpos,
|
| 59 |
+
arm_left_qpos,
|
| 60 |
+
left_gripper_width, # Now normalized [-1, 1]
|
| 61 |
+
arm_right_qpos,
|
| 62 |
+
right_gripper_width, # Now normalized [-1, 1]
|
| 63 |
+
], axis=-1)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _parse_image(image) -> np.ndarray:
|
| 67 |
+
image = np.asarray(image)
|
| 68 |
+
if np.issubdtype(image.dtype, np.floating):
|
| 69 |
+
image = (255 * image).astype(np.uint8)
|
| 70 |
+
if image.shape[0] == 3:
|
| 71 |
+
image = einops.rearrange(image, "c h w -> h w c")
|
| 72 |
+
return image
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
@dataclasses.dataclass(frozen=True)
|
| 76 |
+
class B1kInputs(transforms.DataTransformFn):
|
| 77 |
+
# Determines which model will be used (not actually used in B1K, kept for compatibility)
|
| 78 |
+
model_type: _model.ModelType | str = _model.ModelType.PI0
|
| 79 |
+
|
| 80 |
+
def __call__(self, data: dict) -> dict:
|
| 81 |
+
|
| 82 |
+
proprio_data = data["observation/state"]
|
| 83 |
+
# extract joint position
|
| 84 |
+
state = extract_state_from_proprio(proprio_data)
|
| 85 |
+
if "actions" in data:
|
| 86 |
+
action = data["actions"]
|
| 87 |
+
|
| 88 |
+
# Possibly need to parse images to uint8 (H,W,C) since LeRobot automatically
|
| 89 |
+
# stores as float32 (C,H,W), gets skipped for policy inference
|
| 90 |
+
base_image = _parse_image(data["observation/egocentric_camera"])
|
| 91 |
+
wrist_image_left = _parse_image(data["observation/wrist_image_left"])
|
| 92 |
+
wrist_image_right = _parse_image(data["observation/wrist_image_right"])
|
| 93 |
+
|
| 94 |
+
# For B1K, always use 3 cameras (base, left_wrist, right_wrist)
|
| 95 |
+
names = ("base_0_rgb", "left_wrist_0_rgb", "right_wrist_0_rgb")
|
| 96 |
+
images = (base_image, wrist_image_left, wrist_image_right)
|
| 97 |
+
image_masks = (np.True_, np.True_, np.True_)
|
| 98 |
+
|
| 99 |
+
inputs = {
|
| 100 |
+
"state": state,
|
| 101 |
+
"image": dict(zip(names, images, strict=True)),
|
| 102 |
+
"image_mask": dict(zip(names, image_masks, strict=True)),
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
if "actions" in data:
|
| 106 |
+
inputs["actions"] = action
|
| 107 |
+
|
| 108 |
+
if "prompt" in data:
|
| 109 |
+
inputs["prompt"] = data["prompt"]
|
| 110 |
+
|
| 111 |
+
# Preserve task_index for PI_BEHAVIOR model
|
| 112 |
+
if "task_index" in data:
|
| 113 |
+
inputs["task_index"] = data["task_index"]
|
| 114 |
+
|
| 115 |
+
# Preserve tokenized_prompt for PI_BEHAVIOR model
|
| 116 |
+
if "tokenized_prompt" in data:
|
| 117 |
+
inputs["tokenized_prompt"] = data["tokenized_prompt"]
|
| 118 |
+
if "tokenized_prompt_mask" in data:
|
| 119 |
+
inputs["tokenized_prompt_mask"] = data["tokenized_prompt_mask"]
|
| 120 |
+
|
| 121 |
+
# Preserve subtask_state for PI_BEHAVIOR model
|
| 122 |
+
if "subtask_state" in data:
|
| 123 |
+
inputs["subtask_state"] = data["subtask_state"]
|
| 124 |
+
|
| 125 |
+
# Preserve timestamp and episode_index for subtask state computation
|
| 126 |
+
if "timestamp" in data:
|
| 127 |
+
inputs["timestamp"] = data["timestamp"]
|
| 128 |
+
if "episode_index" in data:
|
| 129 |
+
inputs["episode_index"] = data["episode_index"]
|
| 130 |
+
|
| 131 |
+
# Preserve initial_actions for inpainting
|
| 132 |
+
if "initial_actions" in data:
|
| 133 |
+
initial_actions = data["initial_actions"]
|
| 134 |
+
# Pad initial_actions from 23 dimensions to 32 dimensions (model's action_dim)
|
| 135 |
+
if initial_actions.shape[-1] < 32:
|
| 136 |
+
padding_dim = 32 - initial_actions.shape[-1]
|
| 137 |
+
padding = np.zeros(initial_actions.shape[:-1] + (padding_dim,))
|
| 138 |
+
initial_actions = np.concatenate([initial_actions, padding], axis=-1)
|
| 139 |
+
inputs["initial_actions"] = initial_actions
|
| 140 |
+
|
| 141 |
+
return inputs
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
@dataclasses.dataclass(frozen=True)
|
| 145 |
+
class B1kOutputs(transforms.DataTransformFn):
|
| 146 |
+
def __call__(self, data: dict) -> dict:
|
| 147 |
+
# Return actions (truncated to 23 dims) and preserve subtask predictions
|
| 148 |
+
result = {"actions": np.asarray(data["actions"][:, :23])}
|
| 149 |
+
|
| 150 |
+
# Preserve subtask prediction fields for PI_BEHAVIOR models
|
| 151 |
+
if "subtask_logits" in data:
|
| 152 |
+
result["subtask_logits"] = data["subtask_logits"]
|
| 153 |
+
if "predicted_stage" in data:
|
| 154 |
+
result["predicted_stage"] = data["predicted_stage"]
|
| 155 |
+
|
| 156 |
+
return result
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/policies/pi_behavior_policy.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Minimal Policy subclass for PiBehavior models that handles tuple unpacking.
|
| 2 |
+
|
| 3 |
+
Reference: https://github.com/PhysicalIntelligence/openpi/blob/behavior/src/openpi/policies/policy.py
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import time
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
import jax
|
| 10 |
+
import jax.numpy as jnp
|
| 11 |
+
import numpy as np
|
| 12 |
+
from typing_extensions import override
|
| 13 |
+
|
| 14 |
+
from openpi.policies.policy import Policy
|
| 15 |
+
from b1k.models.observation import Observation # Use our custom Observation with FAST fields
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class PiBehaviorPolicy(Policy):
|
| 19 |
+
"""Policy for PiBehavior models - only difference is unpacking the tuple return.
|
| 20 |
+
|
| 21 |
+
PiBehavior.sample_actions() returns (actions, subtask_logits) instead of just actions.
|
| 22 |
+
This minimal subclass unpacks the tuple before output transforms are applied.
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
@override
|
| 26 |
+
def infer(self, obs: dict, *, noise: np.ndarray | None = None, initial_actions: np.ndarray | None = None) -> dict:
|
| 27 |
+
"""Infer with PiBehavior-specific tuple unpacking.
|
| 28 |
+
|
| 29 |
+
Identical to parent Policy.infer() except:
|
| 30 |
+
1. Accepts initial_actions parameter for rolling inpainting
|
| 31 |
+
2. Unpacks (actions, subtask_logits) tuple before output transforms
|
| 32 |
+
"""
|
| 33 |
+
# Reuse all parent logic for input processing
|
| 34 |
+
inputs = jax.tree.map(lambda x: x, obs)
|
| 35 |
+
inputs = self._input_transform(inputs)
|
| 36 |
+
inputs = jax.tree.map(lambda x: jnp.asarray(x)[np.newaxis, ...], inputs)
|
| 37 |
+
self._rng, sample_rng = jax.random.split(self._rng)
|
| 38 |
+
|
| 39 |
+
# Prepare sample_kwargs
|
| 40 |
+
sample_kwargs = dict(self._sample_kwargs)
|
| 41 |
+
if noise is not None:
|
| 42 |
+
noise = jnp.asarray(noise)
|
| 43 |
+
if noise.ndim == 2:
|
| 44 |
+
noise = noise[None, ...]
|
| 45 |
+
sample_kwargs["noise"] = noise
|
| 46 |
+
|
| 47 |
+
if initial_actions is not None:
|
| 48 |
+
# Create training-format observation batch for proper transform processing
|
| 49 |
+
training_obs = {}
|
| 50 |
+
|
| 51 |
+
# Map evaluation keys to training keys if needed
|
| 52 |
+
if "observation/state" in obs:
|
| 53 |
+
training_obs["observation/state"] = obs["observation/state"]
|
| 54 |
+
elif "state" in obs:
|
| 55 |
+
training_obs["observation/state"] = obs["state"]
|
| 56 |
+
|
| 57 |
+
if "observation/egocentric_camera" in obs:
|
| 58 |
+
training_obs["observation/egocentric_camera"] = obs["observation/egocentric_camera"]
|
| 59 |
+
elif "image" in obs and "base_0_rgb" in obs["image"]:
|
| 60 |
+
training_obs["observation/egocentric_camera"] = obs["image"]["base_0_rgb"]
|
| 61 |
+
|
| 62 |
+
if "observation/wrist_image_left" in obs:
|
| 63 |
+
training_obs["observation/wrist_image_left"] = obs["observation/wrist_image_left"]
|
| 64 |
+
elif "image" in obs and "left_wrist_0_rgb" in obs["image"]:
|
| 65 |
+
training_obs["observation/wrist_image_left"] = obs["image"]["left_wrist_0_rgb"]
|
| 66 |
+
|
| 67 |
+
if "observation/wrist_image_right" in obs:
|
| 68 |
+
training_obs["observation/wrist_image_right"] = obs["observation/wrist_image_right"]
|
| 69 |
+
elif "image" in obs and "right_wrist_0_rgb" in obs["image"]:
|
| 70 |
+
training_obs["observation/wrist_image_right"] = obs["image"]["right_wrist_0_rgb"]
|
| 71 |
+
|
| 72 |
+
# Copy any other keys that might be needed (tokenized_prompt, subtask_state, etc.)
|
| 73 |
+
for key in obs:
|
| 74 |
+
if key not in training_obs and key not in ["image", "state"]:
|
| 75 |
+
training_obs[key] = obs[key]
|
| 76 |
+
|
| 77 |
+
initial_batch = {
|
| 78 |
+
**training_obs, # Include all observation data in training format
|
| 79 |
+
"actions": initial_actions # Add initial_actions as the actions field
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
# Apply the full input transform pipeline (delta transforms + normalization)
|
| 83 |
+
transformed_batch = self._input_transform(initial_batch)
|
| 84 |
+
normalized_initial_actions = transformed_batch["actions"]
|
| 85 |
+
|
| 86 |
+
# Convert to JAX and add batch dim
|
| 87 |
+
initial_actions = jnp.asarray(normalized_initial_actions)
|
| 88 |
+
if initial_actions.ndim == 2:
|
| 89 |
+
initial_actions = initial_actions[None, ...]
|
| 90 |
+
sample_kwargs["initial_actions"] = initial_actions
|
| 91 |
+
|
| 92 |
+
observation = Observation.from_dict(inputs)
|
| 93 |
+
start_time = time.monotonic()
|
| 94 |
+
|
| 95 |
+
# ONLY DIFFERENCE: Unpack tuple return from PiBehavior.sample_actions
|
| 96 |
+
actions, subtask_logits = self._sample_actions(sample_rng, observation, **sample_kwargs)
|
| 97 |
+
|
| 98 |
+
outputs = {
|
| 99 |
+
"state": inputs["state"],
|
| 100 |
+
"actions": actions, # Now an array, not a tuple!
|
| 101 |
+
"subtask_logits": subtask_logits,
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
model_time = time.monotonic() - start_time
|
| 105 |
+
|
| 106 |
+
# Convert to numpy (same as parent)
|
| 107 |
+
outputs = {
|
| 108 |
+
k: np.asarray(v[0, ...]) if isinstance(v, (jnp.ndarray, np.ndarray)) else v
|
| 109 |
+
for k, v in outputs.items()
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
# Apply output transforms (now works because actions is an array)
|
| 113 |
+
outputs = self._output_transform(outputs)
|
| 114 |
+
|
| 115 |
+
# Add convenience field
|
| 116 |
+
outputs["predicted_stage"] = int(np.argmax(outputs["subtask_logits"]))
|
| 117 |
+
|
| 118 |
+
outputs["policy_timing"] = {
|
| 119 |
+
"infer_ms": model_time * 1000,
|
| 120 |
+
}
|
| 121 |
+
return outputs
|
| 122 |
+
|
| 123 |
+
@property
|
| 124 |
+
def metadata(self) -> dict[str, Any]:
|
| 125 |
+
return self._metadata
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/policies/policy_config.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Policy configuration for B1K - loads checkpoints and creates policies.
|
| 2 |
+
|
| 3 |
+
Exact copy of openpi.policies.policy_config but imports b1k.models.pi_behavior.PiBehavior.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
import os
|
| 8 |
+
import pathlib
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
import jax.numpy as jnp
|
| 13 |
+
|
| 14 |
+
import openpi.models.model as _model
|
| 15 |
+
import openpi.policies.policy as _policy
|
| 16 |
+
import openpi.shared.download as download
|
| 17 |
+
import openpi.transforms as transforms
|
| 18 |
+
|
| 19 |
+
# Import B1K-specific modules
|
| 20 |
+
from b1k.models.pi_behavior import PiBehavior
|
| 21 |
+
from b1k.policies.pi_behavior_policy import PiBehaviorPolicy
|
| 22 |
+
from b1k.training import checkpoints as _checkpoints
|
| 23 |
+
from b1k.training import config as _config
|
| 24 |
+
from b1k import transforms as b1k_transforms
|
| 25 |
+
from b1k.transforms_normalize import NormalizeWithPerTimestamp, UnnormalizeWithPerTimestamp
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def create_trained_policy(
|
| 29 |
+
train_config: _config.TrainConfig,
|
| 30 |
+
checkpoint_dir: pathlib.Path | str,
|
| 31 |
+
*,
|
| 32 |
+
repack_transforms: transforms.Group | None = None,
|
| 33 |
+
sample_kwargs: dict[str, Any] | None = None,
|
| 34 |
+
default_prompt: str | None = None,
|
| 35 |
+
norm_stats: dict[str, transforms.NormStats] | None = None,
|
| 36 |
+
pytorch_device: str | None = None,
|
| 37 |
+
) -> _policy.Policy:
|
| 38 |
+
"""Create a policy from a trained checkpoint - EXACT COPY from openpi with b1k imports."""
|
| 39 |
+
repack_transforms = repack_transforms or transforms.Group()
|
| 40 |
+
checkpoint_dir = download.maybe_download(str(checkpoint_dir))
|
| 41 |
+
|
| 42 |
+
# Detect PyTorch model
|
| 43 |
+
is_pytorch = (checkpoint_dir / "pytorch_model.safetensors").exists() or (checkpoint_dir / "pytorch_model.pt").exists()
|
| 44 |
+
|
| 45 |
+
if is_pytorch:
|
| 46 |
+
raise NotImplementedError("PyTorch inference not supported in b1k")
|
| 47 |
+
|
| 48 |
+
# JAX model loading - load directly as bfloat16 to save memory (12GB vs 24GB)
|
| 49 |
+
model = train_config.model.load(_model.restore_params(checkpoint_dir / "params", dtype=jnp.bfloat16))
|
| 50 |
+
|
| 51 |
+
# Get data config
|
| 52 |
+
data_config = train_config.data.create(train_config.assets_dirs, train_config.model)
|
| 53 |
+
|
| 54 |
+
# Load norm stats if not provided
|
| 55 |
+
if norm_stats is None:
|
| 56 |
+
if data_config.asset_id is None:
|
| 57 |
+
raise ValueError("Asset id is required to load norm stats.")
|
| 58 |
+
norm_stats = _checkpoints.load_norm_stats(checkpoint_dir / "assets", data_config.asset_id)
|
| 59 |
+
|
| 60 |
+
# Load correlation matrix for PiBehavior models
|
| 61 |
+
if isinstance(model, PiBehavior):
|
| 62 |
+
if norm_stats is None:
|
| 63 |
+
raise ValueError("PiBehavior requires norm_stats but none found.")
|
| 64 |
+
model.load_correlation_matrix(norm_stats)
|
| 65 |
+
logging.info("Loaded correlation matrix for inference")
|
| 66 |
+
|
| 67 |
+
# Determine the device for PyTorch (not used for b1k but kept for compatibility)
|
| 68 |
+
if is_pytorch and pytorch_device is None:
|
| 69 |
+
try:
|
| 70 |
+
import torch
|
| 71 |
+
pytorch_device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 72 |
+
except ImportError:
|
| 73 |
+
pytorch_device = "cpu"
|
| 74 |
+
|
| 75 |
+
# For PI_BEHAVIOR models during inference, skip training-specific transforms
|
| 76 |
+
model_transforms_inputs = []
|
| 77 |
+
for transform in data_config.model_transforms.inputs:
|
| 78 |
+
# Skip training-specific transforms during inference
|
| 79 |
+
if isinstance(transform, (b1k_transforms.ComputeSubtaskStateFromMeta, b1k_transforms.TaskIndexToTaskId, b1k_transforms.TokenizeFASTActions)):
|
| 80 |
+
continue
|
| 81 |
+
model_transforms_inputs.append(transform)
|
| 82 |
+
|
| 83 |
+
# Build input transform pipeline (skip data_config.repack_transforms - has 'actions' mapping for training)
|
| 84 |
+
input_transforms = [
|
| 85 |
+
*repack_transforms.inputs,
|
| 86 |
+
transforms.InjectDefaultPrompt(default_prompt),
|
| 87 |
+
*data_config.data_transforms.inputs,
|
| 88 |
+
NormalizeWithPerTimestamp(norm_stats, use_quantiles=data_config.use_quantile_norm, use_per_timestamp=data_config.use_per_timestamp_norm),
|
| 89 |
+
*model_transforms_inputs,
|
| 90 |
+
]
|
| 91 |
+
|
| 92 |
+
# Build output transform pipeline
|
| 93 |
+
output_transforms = [
|
| 94 |
+
*data_config.model_transforms.outputs,
|
| 95 |
+
UnnormalizeWithPerTimestamp(norm_stats, use_quantiles=data_config.use_quantile_norm, use_per_timestamp=data_config.use_per_timestamp_norm),
|
| 96 |
+
*data_config.data_transforms.outputs,
|
| 97 |
+
*repack_transforms.outputs,
|
| 98 |
+
]
|
| 99 |
+
|
| 100 |
+
# Use custom PiBehaviorPolicy for PiBehavior models (handles tuple unpacking)
|
| 101 |
+
if isinstance(model, PiBehavior):
|
| 102 |
+
return PiBehaviorPolicy(
|
| 103 |
+
model,
|
| 104 |
+
transforms=input_transforms,
|
| 105 |
+
output_transforms=output_transforms,
|
| 106 |
+
sample_kwargs=sample_kwargs,
|
| 107 |
+
metadata=train_config.policy_metadata,
|
| 108 |
+
)
|
| 109 |
+
else:
|
| 110 |
+
return _policy.Policy(
|
| 111 |
+
model,
|
| 112 |
+
transforms=input_transforms,
|
| 113 |
+
output_transforms=output_transforms,
|
| 114 |
+
sample_kwargs=sample_kwargs,
|
| 115 |
+
metadata=train_config.policy_metadata,
|
| 116 |
+
is_pytorch=is_pytorch,
|
| 117 |
+
pytorch_device=pytorch_device if is_pytorch else "cpu",
|
| 118 |
+
)
|
| 119 |
+
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/b1k_2026.py
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""BEHAVIOR-1K 2026 (LeRobot v3.0) data loading for the 2025 PiBehavior model.
|
| 2 |
+
|
| 3 |
+
The 2025 winner's loader targets OmniGibson's v2.1 `BehaviorLeRobotDataset`
|
| 4 |
+
(task-partitioned, 256-dim proprio). The 2026 challenge dataset is LeRobot
|
| 5 |
+
**v3.0** (chunk-based `data/chunk-XXX/file-XXX.parquet`, RGB in videos, 61-dim
|
| 6 |
+
proprio, 100 tasks). The pinned lerobot (v2.1) cannot read it, so this module
|
| 7 |
+
provides a self-contained v3 reader that yields items in the exact dict format
|
| 8 |
+
the existing transform pipeline expects, plus the two remaps needed:
|
| 9 |
+
|
| 10 |
+
* camera keys: zed_link -> head, left/right_realsense -> left/right_wrist
|
| 11 |
+
* task_index: 2026 index -> the 2025 index the checkpoint's task-embedding
|
| 12 |
+
table is keyed on (via activity name <-> task_data.json)
|
| 13 |
+
|
| 14 |
+
Videos are decoded with PyAV (torchcodec's ffmpeg libs are absent here).
|
| 15 |
+
"""
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import dataclasses
|
| 19 |
+
import functools
|
| 20 |
+
import glob
|
| 21 |
+
import hashlib
|
| 22 |
+
import json
|
| 23 |
+
import logging
|
| 24 |
+
import os
|
| 25 |
+
import time
|
| 26 |
+
from collections import OrderedDict
|
| 27 |
+
from typing import Dict, List, Optional
|
| 28 |
+
|
| 29 |
+
import av
|
| 30 |
+
import einops
|
| 31 |
+
import numpy as np
|
| 32 |
+
import pandas as pd
|
| 33 |
+
import torch
|
| 34 |
+
|
| 35 |
+
from openpi import transforms
|
| 36 |
+
from openpi.models import model as _model
|
| 37 |
+
|
| 38 |
+
logger = logging.getLogger("b1k.2026")
|
| 39 |
+
|
| 40 |
+
# ---- 2026 R1Pro proprioception layout (61-dim), from BEHAVIOR-1K main
|
| 41 |
+
# OmniGibson/omnigibson/eval/utils/eval_utils.py::PROPRIOCEPTION_INDICES["R1Pro"]
|
| 42 |
+
PROP_2026 = {
|
| 43 |
+
"base_qvel": slice(0, 3),
|
| 44 |
+
"arm_left_qpos": slice(3, 10),
|
| 45 |
+
"gripper_left_qpos": slice(24, 26),
|
| 46 |
+
"arm_right_qpos": slice(28, 35),
|
| 47 |
+
"gripper_right_qpos": slice(49, 51),
|
| 48 |
+
"trunk_qpos": slice(53, 57),
|
| 49 |
+
}
|
| 50 |
+
MAX_GRIPPER_WIDTH = 0.1 # matches the 2025 gripper normalization
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def extract_state_2026(proprio: np.ndarray) -> np.ndarray:
|
| 54 |
+
"""61-dim 2026 proprio -> 23-dim model state, in the SAME field order the
|
| 55 |
+
2025 `extract_state_from_proprio` produced (base_qvel, trunk, arm_left,
|
| 56 |
+
gripper_left, arm_right, gripper_right)."""
|
| 57 |
+
p = np.asarray(proprio, dtype=np.float32)
|
| 58 |
+
base_qvel = p[..., PROP_2026["base_qvel"]] # 3
|
| 59 |
+
trunk_qpos = p[..., PROP_2026["trunk_qpos"]] # 4
|
| 60 |
+
arm_left = p[..., PROP_2026["arm_left_qpos"]] # 7
|
| 61 |
+
arm_right = p[..., PROP_2026["arm_right_qpos"]] # 7
|
| 62 |
+
lg = p[..., PROP_2026["gripper_left_qpos"]].sum(-1, keepdims=True)
|
| 63 |
+
rg = p[..., PROP_2026["gripper_right_qpos"]].sum(-1, keepdims=True)
|
| 64 |
+
lg = 2.0 * (lg / MAX_GRIPPER_WIDTH) - 1.0 # -> [-1,1]
|
| 65 |
+
rg = 2.0 * (rg / MAX_GRIPPER_WIDTH) - 1.0
|
| 66 |
+
return np.concatenate([base_qvel, trunk_qpos, arm_left, lg, arm_right, rg], axis=-1)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
@dataclasses.dataclass(frozen=True)
|
| 70 |
+
class B1kInputs2026(transforms.DataTransformFn):
|
| 71 |
+
"""Same as b1k_policy.B1kInputs but with 2026 61-dim state extraction and
|
| 72 |
+
no OmniGibson dependency."""
|
| 73 |
+
model_type: object = _model.ModelType.PI0
|
| 74 |
+
|
| 75 |
+
def __call__(self, data: dict) -> dict:
|
| 76 |
+
state = extract_state_2026(data["observation/state"])
|
| 77 |
+
|
| 78 |
+
def _img(x):
|
| 79 |
+
x = np.asarray(x)
|
| 80 |
+
if np.issubdtype(x.dtype, np.floating):
|
| 81 |
+
x = (255 * x).astype(np.uint8)
|
| 82 |
+
if x.shape[0] == 3:
|
| 83 |
+
x = einops.rearrange(x, "c h w -> h w c")
|
| 84 |
+
return x
|
| 85 |
+
|
| 86 |
+
names = ("base_0_rgb", "left_wrist_0_rgb", "right_wrist_0_rgb")
|
| 87 |
+
imgs = (_img(data["observation/egocentric_camera"]),
|
| 88 |
+
_img(data["observation/wrist_image_left"]),
|
| 89 |
+
_img(data["observation/wrist_image_right"]))
|
| 90 |
+
out = {
|
| 91 |
+
"state": state,
|
| 92 |
+
"image": dict(zip(names, imgs, strict=True)),
|
| 93 |
+
"image_mask": {n: np.True_ for n in names},
|
| 94 |
+
}
|
| 95 |
+
for k in ("actions", "task_index", "timestamp", "episode_index",
|
| 96 |
+
"tokenized_prompt", "tokenized_prompt_mask", "subtask_state"):
|
| 97 |
+
if k in data:
|
| 98 |
+
out[k] = data[k]
|
| 99 |
+
return out
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
class _V3Meta:
|
| 103 |
+
"""Minimal `dataset.meta` shim for ComputeSubtaskStateFromMeta.
|
| 104 |
+
`.episodes` maps episode_index -> {'length': int}."""
|
| 105 |
+
def __init__(self, episodes: Dict[int, dict]):
|
| 106 |
+
self.episodes = episodes
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
# --------------------------------------------------------------------------- #
|
| 110 |
+
# task-index remapping: 2026 index -> 2025 index (what the checkpoint knows)
|
| 111 |
+
# --------------------------------------------------------------------------- #
|
| 112 |
+
def build_task_index_maps(root_2026: str, task_data_json: str):
|
| 113 |
+
"""Return (name->2025idx, 2026idx->2025idx, name->2026idx)."""
|
| 114 |
+
td = json.load(open(task_data_json))["tasks"]
|
| 115 |
+
name2025 = {t["id"]: i for i, t in enumerate(td)} # activity -> 2025 idx
|
| 116 |
+
dt = pd.read_parquet(os.path.join(root_2026, "meta", "tasks.parquet"))
|
| 117 |
+
# tasks.parquet: index = activity name, column task_index (2026)
|
| 118 |
+
name2026 = {name: int(row["task_index"]) for name, row in dt.iterrows()}
|
| 119 |
+
idx2026_to_2025 = {name2026[n]: name2025[n] for n in name2026 if n in name2025}
|
| 120 |
+
return name2025, idx2026_to_2025, name2026
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
class BehaviorV3Dataset(torch.utils.data.Dataset):
|
| 124 |
+
"""LeRobot v3.0 reader for a subset of activities. Yields per-frame items in
|
| 125 |
+
the 2025-style LeRobot dict format (pre-repack keys), with task_index already
|
| 126 |
+
remapped to the 2025 index and RGB decoded to uint8 HWC."""
|
| 127 |
+
|
| 128 |
+
RGB_KEYS = OrderedDict([
|
| 129 |
+
("observation.images.rgb.head", "observation.rgb.zed_link_camera_0"),
|
| 130 |
+
("observation.images.rgb.left_wrist", "observation.rgb.left_realsense_link_camera_0"),
|
| 131 |
+
("observation.images.rgb.right_wrist", "observation.rgb.right_realsense_link_camera_0"),
|
| 132 |
+
])
|
| 133 |
+
|
| 134 |
+
def __init__(self, root: str, activities: List[str], action_horizon: int,
|
| 135 |
+
task_data_json: str, seed: int = 0, parquet_cache: int = 16):
|
| 136 |
+
self.root = root
|
| 137 |
+
self.H = int(action_horizon)
|
| 138 |
+
self.fps = float(json.load(open(os.path.join(root, "meta", "info.json")))["fps"])
|
| 139 |
+
_, self.idx2026_to_2025, self.name2026 = build_task_index_maps(root, task_data_json)
|
| 140 |
+
acts = set(activities)
|
| 141 |
+
|
| 142 |
+
# episode metadata (filtered to our activities)
|
| 143 |
+
ep = pd.concat([pd.read_parquet(f) for f in sorted(
|
| 144 |
+
glob.glob(os.path.join(root, "meta", "episodes", "**", "*.parquet"), recursive=True))],
|
| 145 |
+
ignore_index=True)
|
| 146 |
+
ep["task0"] = ep["tasks"].apply(lambda v: v[0] if hasattr(v, "__len__") and not isinstance(v, str) else v)
|
| 147 |
+
ep = ep[ep["task0"].isin(acts)].reset_index(drop=True)
|
| 148 |
+
|
| 149 |
+
self.episodes: List[dict] = []
|
| 150 |
+
meta_eps: Dict[int, dict] = {}
|
| 151 |
+
samples: List[tuple] = []
|
| 152 |
+
for _, r in ep.iterrows():
|
| 153 |
+
E = int(r["episode_index"]); L = int(r["length"])
|
| 154 |
+
rec = {
|
| 155 |
+
"episode_index": E, "length": L, "task0": r["task0"],
|
| 156 |
+
"data": os.path.join(root, "data", f"chunk-{int(r['data/chunk_index']):03d}",
|
| 157 |
+
f"file-{int(r['data/file_index']):03d}.parquet"),
|
| 158 |
+
"video": {}, "from_ts": {},
|
| 159 |
+
}
|
| 160 |
+
for dst, src in self.RGB_KEYS.items():
|
| 161 |
+
rec["video"][dst] = os.path.join(
|
| 162 |
+
root, "videos", src,
|
| 163 |
+
f"chunk-{int(r[f'videos/{src}/chunk_index']):03d}",
|
| 164 |
+
f"file-{int(r[f'videos/{src}/file_index']):03d}.mp4")
|
| 165 |
+
rec["from_ts"][dst] = float(r[f"videos/{src}/from_timestamp"])
|
| 166 |
+
ei = len(self.episodes)
|
| 167 |
+
self.episodes.append(rec)
|
| 168 |
+
meta_eps[E] = {"length": L}
|
| 169 |
+
# only frames with a full future action window
|
| 170 |
+
for t in range(max(1, L - self.H)):
|
| 171 |
+
samples.append((ei, t))
|
| 172 |
+
self.samples = samples
|
| 173 |
+
self.meta = _V3Meta(meta_eps)
|
| 174 |
+
self._pq_cache: "OrderedDict[str, pd.DataFrame]" = OrderedDict()
|
| 175 |
+
self._pq_cache_max = parquet_cache
|
| 176 |
+
self._video_cache: "OrderedDict[str, av.container.InputContainer]" = OrderedDict()
|
| 177 |
+
self._video_cache_max = int(os.environ.get("B1K_VIDEO_CACHE_SIZE", "12"))
|
| 178 |
+
self._decode_resize = int(os.environ.get("B1K_DECODE_RESIZE", "224"))
|
| 179 |
+
self._frame_cache_dir = os.environ.get("B1K_FRAME_CACHE_DIR")
|
| 180 |
+
self._frame_cache_max_bytes = int(float(os.environ.get("B1K_FRAME_CACHE_MAX_GB", "4")) * (1024 ** 3))
|
| 181 |
+
self._frame_cache_prune_every = max(1, int(os.environ.get("B1K_FRAME_CACHE_PRUNE_EVERY", "2048")))
|
| 182 |
+
self._frame_cache_checks = 0
|
| 183 |
+
self._frame_mem_cache: "OrderedDict[tuple[str, int], np.ndarray]" = OrderedDict()
|
| 184 |
+
self._frame_mem_cache_bytes = 0
|
| 185 |
+
self._frame_mem_cache_max_bytes = int(float(os.environ.get("B1K_FRAME_MEM_CACHE_GB", "0")) * (1024 ** 3))
|
| 186 |
+
self._frame_cache_touch_disk = os.environ.get("B1K_FRAME_CACHE_TOUCH", "0") == "1"
|
| 187 |
+
if self._frame_cache_dir:
|
| 188 |
+
os.makedirs(self._frame_cache_dir, exist_ok=True)
|
| 189 |
+
if os.environ.get("B1K_FRAME_CACHE_PRUNE_ON_INIT", "0") == "1":
|
| 190 |
+
self._prune_frame_cache(force=True)
|
| 191 |
+
logger.info("BehaviorV3Dataset: %d episodes, %d frame-samples, %d activities",
|
| 192 |
+
len(self.episodes), len(self.samples), len(acts))
|
| 193 |
+
|
| 194 |
+
def __getstate__(self):
|
| 195 |
+
state = self.__dict__.copy()
|
| 196 |
+
state["_pq_cache"] = OrderedDict()
|
| 197 |
+
state["_video_cache"] = OrderedDict()
|
| 198 |
+
state["_frame_mem_cache"] = OrderedDict()
|
| 199 |
+
state["_frame_mem_cache_bytes"] = 0
|
| 200 |
+
return state
|
| 201 |
+
|
| 202 |
+
def __del__(self):
|
| 203 |
+
for container in getattr(self, "_video_cache", {}).values():
|
| 204 |
+
try:
|
| 205 |
+
container.close()
|
| 206 |
+
except Exception:
|
| 207 |
+
pass
|
| 208 |
+
|
| 209 |
+
def __len__(self):
|
| 210 |
+
return len(self.samples)
|
| 211 |
+
|
| 212 |
+
def _episode_frames(self, rec) -> pd.DataFrame:
|
| 213 |
+
"""Cached per-episode frame table (state, action, timestamp), sorted."""
|
| 214 |
+
key = rec["data"]
|
| 215 |
+
if key not in self._pq_cache:
|
| 216 |
+
df = pd.read_parquet(key, columns=["episode_index", "frame_index",
|
| 217 |
+
"observation.state", "action",
|
| 218 |
+
"timestamp", "task_index"])
|
| 219 |
+
self._pq_cache[key] = df
|
| 220 |
+
if len(self._pq_cache) > self._pq_cache_max:
|
| 221 |
+
self._pq_cache.popitem(last=False)
|
| 222 |
+
df = self._pq_cache[key]
|
| 223 |
+
sub = df[df["episode_index"] == rec["episode_index"]].sort_values("frame_index")
|
| 224 |
+
return sub
|
| 225 |
+
|
| 226 |
+
def _cached_container(self, path: str):
|
| 227 |
+
container = self._video_cache.get(path)
|
| 228 |
+
if container is not None:
|
| 229 |
+
self._video_cache.move_to_end(path)
|
| 230 |
+
return container
|
| 231 |
+
|
| 232 |
+
container = av.open(path)
|
| 233 |
+
# Cap ffmpeg decode threads per stream. HEVC's default thread_type=AUTO spawns up to ncores
|
| 234 |
+
# (240 here) threads PER container; with many workers x 6 containers/sample this blows past the
|
| 235 |
+
# kernel/cgroup thread ceiling ("can't start new thread"). A small fixed count is plenty since
|
| 236 |
+
# parallelism comes from the dataloader workers, not per-decode threads.
|
| 237 |
+
_dt = int(os.environ.get("B1K_DECODE_THREADS", "1"))
|
| 238 |
+
try:
|
| 239 |
+
vs0 = container.streams.video[0]
|
| 240 |
+
vs0.thread_count = _dt
|
| 241 |
+
vs0.thread_type = "NONE" if _dt <= 1 else "FRAME"
|
| 242 |
+
except Exception:
|
| 243 |
+
pass
|
| 244 |
+
self._video_cache[path] = container
|
| 245 |
+
if len(self._video_cache) > self._video_cache_max:
|
| 246 |
+
_, old = self._video_cache.popitem(last=False)
|
| 247 |
+
old.close()
|
| 248 |
+
return container
|
| 249 |
+
|
| 250 |
+
def _frame_cache_path(self, path: str, frame_idx: int) -> str | None:
|
| 251 |
+
if not self._frame_cache_dir:
|
| 252 |
+
return None
|
| 253 |
+
key = hashlib.blake2b(f"{path}|{frame_idx}|{self._decode_resize}".encode(), digest_size=16).hexdigest()
|
| 254 |
+
return os.path.join(self._frame_cache_dir, f"{key}.npy")
|
| 255 |
+
|
| 256 |
+
def _get_frame_mem_cache(self, key: tuple[str, int]) -> np.ndarray | None:
|
| 257 |
+
if self._frame_mem_cache_max_bytes <= 0:
|
| 258 |
+
return None
|
| 259 |
+
img = self._frame_mem_cache.get(key)
|
| 260 |
+
if img is None:
|
| 261 |
+
return None
|
| 262 |
+
self._frame_mem_cache.move_to_end(key)
|
| 263 |
+
return img
|
| 264 |
+
|
| 265 |
+
def _put_frame_mem_cache(self, key: tuple[str, int], img: np.ndarray) -> None:
|
| 266 |
+
if self._frame_mem_cache_max_bytes <= 0:
|
| 267 |
+
return
|
| 268 |
+
old = self._frame_mem_cache.pop(key, None)
|
| 269 |
+
if old is not None:
|
| 270 |
+
self._frame_mem_cache_bytes -= old.nbytes
|
| 271 |
+
self._frame_mem_cache[key] = img
|
| 272 |
+
self._frame_mem_cache_bytes += img.nbytes
|
| 273 |
+
while self._frame_mem_cache_bytes > self._frame_mem_cache_max_bytes and self._frame_mem_cache:
|
| 274 |
+
_, evicted = self._frame_mem_cache.popitem(last=False)
|
| 275 |
+
self._frame_mem_cache_bytes -= evicted.nbytes
|
| 276 |
+
|
| 277 |
+
def _prune_frame_cache(self, *, force: bool = False):
|
| 278 |
+
if not self._frame_cache_dir or self._frame_cache_max_bytes <= 0:
|
| 279 |
+
return
|
| 280 |
+
self._frame_cache_checks += 1
|
| 281 |
+
if not force and self._frame_cache_checks % self._frame_cache_prune_every:
|
| 282 |
+
return
|
| 283 |
+
files = []
|
| 284 |
+
total = 0
|
| 285 |
+
for p in glob.glob(os.path.join(self._frame_cache_dir, "*.npy")):
|
| 286 |
+
try:
|
| 287 |
+
st = os.stat(p)
|
| 288 |
+
except FileNotFoundError:
|
| 289 |
+
continue
|
| 290 |
+
total += st.st_size
|
| 291 |
+
files.append((st.st_mtime, st.st_size, p))
|
| 292 |
+
if total <= self._frame_cache_max_bytes:
|
| 293 |
+
return
|
| 294 |
+
for _, size, p in sorted(files):
|
| 295 |
+
try:
|
| 296 |
+
os.remove(p)
|
| 297 |
+
total -= size
|
| 298 |
+
except FileNotFoundError:
|
| 299 |
+
pass
|
| 300 |
+
if total <= int(self._frame_cache_max_bytes * 0.85):
|
| 301 |
+
break
|
| 302 |
+
|
| 303 |
+
def _decode_rgb(self, path: str, ts: float) -> np.ndarray:
|
| 304 |
+
frame_idx = int(round(ts * self.fps))
|
| 305 |
+
mem_key = (path, frame_idx)
|
| 306 |
+
cached_img = self._get_frame_mem_cache(mem_key)
|
| 307 |
+
if cached_img is not None:
|
| 308 |
+
return cached_img
|
| 309 |
+
cache_path = self._frame_cache_path(path, frame_idx)
|
| 310 |
+
if cache_path and os.path.exists(cache_path):
|
| 311 |
+
try:
|
| 312 |
+
if self._frame_cache_touch_disk:
|
| 313 |
+
os.utime(cache_path, None)
|
| 314 |
+
img = np.load(cache_path)
|
| 315 |
+
self._put_frame_mem_cache(mem_key, img)
|
| 316 |
+
return img
|
| 317 |
+
except Exception:
|
| 318 |
+
try:
|
| 319 |
+
os.remove(cache_path)
|
| 320 |
+
except FileNotFoundError:
|
| 321 |
+
pass
|
| 322 |
+
|
| 323 |
+
container = self._cached_container(path)
|
| 324 |
+
vs = container.streams.video[0]
|
| 325 |
+
container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
|
| 326 |
+
frame = None
|
| 327 |
+
for fr in container.decode(vs):
|
| 328 |
+
if fr.time is not None and fr.time >= ts - 1e-3:
|
| 329 |
+
frame = fr
|
| 330 |
+
break
|
| 331 |
+
if frame is None: # ts past end — take last decoded
|
| 332 |
+
container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
|
| 333 |
+
for fr in container.decode(vs):
|
| 334 |
+
frame = fr
|
| 335 |
+
if self._decode_resize > 0:
|
| 336 |
+
frame = frame.reformat(width=self._decode_resize, height=self._decode_resize, format="rgb24")
|
| 337 |
+
img = frame.to_ndarray(format="rgb24")
|
| 338 |
+
self._put_frame_mem_cache(mem_key, img)
|
| 339 |
+
if cache_path:
|
| 340 |
+
self._prune_frame_cache()
|
| 341 |
+
tmp = f"{cache_path}.{os.getpid()}.{time.time_ns()}.tmp"
|
| 342 |
+
try:
|
| 343 |
+
with open(tmp, "wb") as f:
|
| 344 |
+
np.save(f, img)
|
| 345 |
+
os.replace(tmp, cache_path)
|
| 346 |
+
except Exception:
|
| 347 |
+
try:
|
| 348 |
+
os.remove(tmp)
|
| 349 |
+
except FileNotFoundError:
|
| 350 |
+
pass
|
| 351 |
+
return img # HWC uint8
|
| 352 |
+
|
| 353 |
+
def __getitem__(self, i):
|
| 354 |
+
ei, t = self.samples[i]
|
| 355 |
+
rec = self.episodes[ei]
|
| 356 |
+
sub = self._episode_frames(rec)
|
| 357 |
+
states = np.stack(sub["observation.state"].to_numpy()) # [L,61]
|
| 358 |
+
actions = np.stack(sub["action"].to_numpy()) # [L,23]
|
| 359 |
+
ts = float(sub["timestamp"].iloc[t])
|
| 360 |
+
act_win = actions[t:t + self.H] # [H,23]
|
| 361 |
+
item = {
|
| 362 |
+
"observation.state": states[t].astype(np.float32), # raw 61-dim
|
| 363 |
+
"action": act_win.astype(np.float32),
|
| 364 |
+
"task_index": np.int64(self.idx2026_to_2025[int(sub["task_index"].iloc[t])]),
|
| 365 |
+
"timestamp": np.float32(ts),
|
| 366 |
+
"episode_index": np.int64(rec["episode_index"]),
|
| 367 |
+
"index": np.int64(i),
|
| 368 |
+
}
|
| 369 |
+
for dst in self.RGB_KEYS:
|
| 370 |
+
frame_ts = rec["from_ts"][dst] + t / self.fps
|
| 371 |
+
item[dst] = self._decode_rgb(rec["video"][dst], frame_ts) # HWC uint8
|
| 372 |
+
return item
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
def create_v3_behavior_data_loader(config, root_2026: str, activities: List[str],
|
| 376 |
+
task_data_json: str, *, sharding=None,
|
| 377 |
+
shuffle: bool = True, num_workers: Optional[int] = None,
|
| 378 |
+
seed: int = 0):
|
| 379 |
+
"""Build a training data loader over the 2026 v3 subset, reusing the 2025
|
| 380 |
+
transform pipeline but with 2026 state extraction (B1kInputs2026)."""
|
| 381 |
+
import jax
|
| 382 |
+
import dataclasses as _dc
|
| 383 |
+
from b1k.policies import b1k_policy
|
| 384 |
+
from b1k.training.data_loader import transform_dataset, DataLoaderImpl
|
| 385 |
+
from openpi.training.data_loader import TorchDataLoader
|
| 386 |
+
|
| 387 |
+
data_config = config.data.create(config.assets_dirs, config.model)
|
| 388 |
+
# Swap the OmniGibson-dependent B1kInputs for the 2026 61-dim variant.
|
| 389 |
+
new_inputs = tuple(
|
| 390 |
+
B1kInputs2026(model_type=config.model.model_type)
|
| 391 |
+
if isinstance(x, b1k_policy.B1kInputs) else x
|
| 392 |
+
for x in data_config.data_transforms.inputs
|
| 393 |
+
)
|
| 394 |
+
data_config = _dc.replace(
|
| 395 |
+
data_config,
|
| 396 |
+
data_transforms=_dc.replace(data_config.data_transforms, inputs=new_inputs),
|
| 397 |
+
)
|
| 398 |
+
|
| 399 |
+
ds = BehaviorV3Dataset(root_2026, activities=activities,
|
| 400 |
+
action_horizon=config.model.action_horizon,
|
| 401 |
+
task_data_json=task_data_json, seed=seed)
|
| 402 |
+
ds = transform_dataset(ds, data_config) # adds dataset-aware subtask + per-ts norm
|
| 403 |
+
loader = TorchDataLoader(
|
| 404 |
+
ds,
|
| 405 |
+
local_batch_size=config.batch_size // jax.process_count(),
|
| 406 |
+
sharding=sharding, shuffle=shuffle,
|
| 407 |
+
num_workers=config.num_workers if num_workers is None else num_workers,
|
| 408 |
+
seed=seed,
|
| 409 |
+
)
|
| 410 |
+
return DataLoaderImpl(data_config, loader)
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
__all__ = ["BehaviorV3Dataset", "B1kInputs2026", "extract_state_2026",
|
| 414 |
+
"build_task_index_maps", "PROP_2026", "create_v3_behavior_data_loader"]
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/b1k_da3.py
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""DA3 spatial inputs for the 2026 v3 pipeline.
|
| 2 |
+
|
| 3 |
+
Extends BehaviorV3Dataset with per-frame DA3 inputs (3 cams at DA3 resolution + robot->cam
|
| 4 |
+
OpenCV extrinsics + intrinsics + ModernBERT task-language), and provides a data-loader factory
|
| 5 |
+
that runs the frozen DA3-GIANT extractor once per BATCH (GPU) via the loader's batch hook.
|
| 6 |
+
|
| 7 |
+
Geometry (empirically calibrated against GT depth, see /work/jack/behavior1k/calib):
|
| 8 |
+
* robot2cam_pose[7] = [pos(3), quat_wxyz(4)] = the CAMERA POSE IN THE ROBOT FRAME,
|
| 9 |
+
already OpenCV-convention (+Z optical axis). robot->cam = inv(pose_matrix).
|
| 10 |
+
* intrinsics: fx = fy = W * 17.0/20.995 (OmniGibson VisionSensor defaults), cx=cy=W/2.
|
| 11 |
+
"""
|
| 12 |
+
import logging
|
| 13 |
+
import os
|
| 14 |
+
import pickle
|
| 15 |
+
import glob
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
|
| 19 |
+
from b1k.training.b1k_2026 import BehaviorV3Dataset, B1kInputs2026
|
| 20 |
+
|
| 21 |
+
logger = logging.getLogger(__name__)
|
| 22 |
+
|
| 23 |
+
FOCAL_RATIO = 17.0 / 20.995 # OmniGibson VisionSensor default focal/aperture
|
| 24 |
+
|
| 25 |
+
# dst rgb key -> pose parquet column (same camera)
|
| 26 |
+
POSE_COLS = {
|
| 27 |
+
"observation.images.rgb.head": "observation.robot2cam_pose.zed_link_camera_0",
|
| 28 |
+
"observation.images.rgb.left_wrist": "observation.robot2cam_pose.left_realsense_link_camera_0",
|
| 29 |
+
"observation.images.rgb.right_wrist": "observation.robot2cam_pose.right_realsense_link_camera_0",
|
| 30 |
+
}
|
| 31 |
+
# view order MUST match the bank builder: 0=main(head), 1=left, 2=right
|
| 32 |
+
VIEW_ORDER = (
|
| 33 |
+
"observation.images.rgb.head",
|
| 34 |
+
"observation.images.rgb.left_wrist",
|
| 35 |
+
"observation.images.rgb.right_wrist",
|
| 36 |
+
)
|
| 37 |
+
DEPTH_SRCS = {
|
| 38 |
+
"observation.images.rgb.head": "observation.depth_linear.zed_link_camera_0",
|
| 39 |
+
"observation.images.rgb.left_wrist": "observation.depth_linear.left_realsense_link_camera_0",
|
| 40 |
+
"observation.images.rgb.right_wrist": "observation.depth_linear.right_realsense_link_camera_0",
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def quat_wxyz_to_R(q):
|
| 45 |
+
w, x, y, z = q / (np.linalg.norm(q) + 1e-12)
|
| 46 |
+
return np.array([
|
| 47 |
+
[1 - 2 * (y * y + z * z), 2 * (x * y - w * z), 2 * (x * z + w * y)],
|
| 48 |
+
[2 * (x * y + w * z), 1 - 2 * (x * x + z * z), 2 * (y * z - w * x)],
|
| 49 |
+
[2 * (x * z - w * y), 2 * (y * z + w * x), 1 - 2 * (x * x + y * y)],
|
| 50 |
+
])
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# OmniGibson cameras use the OpenGL optical convention (-Z forward, +Y up); the DA3 pinhole
|
| 54 |
+
# projection assumes OpenCV (+Z forward, +Y down). This diag(1,-1,-1) flips the camera Y/Z axes.
|
| 55 |
+
# WITHOUT it, cross-view GT-depth reprojection is 0.00 (cameras point the wrong way); WITH it, 0.20+
|
| 56 |
+
# (best of all 8 conventions), and the head camera lands at its true +1.56 m height. (calib/calibrate_v3.py)
|
| 57 |
+
_GL2CV = np.diag([1.0, -1.0, -1.0, 1.0]).astype(np.float32)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def pose7_to_robot2cam(p7: np.ndarray) -> np.ndarray:
|
| 61 |
+
"""[pos3, quat_wxyz] camera-pose-in-robot-frame -> 4x4 robot->cam (OpenCV).
|
| 62 |
+
|
| 63 |
+
Convention (validated in calib/): quat=wxyz, pose is the CAMERA-IN-ROBOT transform so
|
| 64 |
+
robot->cam = inv(T), then GL->CV optical flip.
|
| 65 |
+
"""
|
| 66 |
+
T = np.eye(4, dtype=np.float32)
|
| 67 |
+
T[:3, :3] = quat_wxyz_to_R(np.asarray(p7[3:], np.float64))
|
| 68 |
+
T[:3, 3] = p7[:3]
|
| 69 |
+
return (_GL2CV @ np.linalg.inv(T)).astype(np.float32)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class BehaviorV3DA3Dataset(BehaviorV3Dataset):
|
| 73 |
+
"""BehaviorV3Dataset + DA3 inputs (frames @ da3_hw, extrinsics, intrinsics, task language)."""
|
| 74 |
+
|
| 75 |
+
def __init__(self, *args, da3_hw=(252, 252), lang_cache: str | None = None, lang_max_len: int = 32, **kwargs):
|
| 76 |
+
super().__init__(*args, **kwargs)
|
| 77 |
+
self._da3_hw = tuple(da3_hw)
|
| 78 |
+
self._lang_max_len = int(lang_max_len)
|
| 79 |
+
self._lang = None
|
| 80 |
+
if lang_cache:
|
| 81 |
+
with open(lang_cache, "rb") as f:
|
| 82 |
+
self._lang = pickle.load(f)
|
| 83 |
+
logger.info("DA3 lang cache: %d tasks from %s", len(self._lang), lang_cache)
|
| 84 |
+
if os.environ.get("B1K_USE_GT_DEPTH") == "1":
|
| 85 |
+
import pandas as pd
|
| 86 |
+
depth_meta = pd.concat(
|
| 87 |
+
[pd.read_parquet(f) for f in sorted(glob.glob(
|
| 88 |
+
os.path.join(self.root, "meta", "episodes", "**", "*.parquet"), recursive=True
|
| 89 |
+
))],
|
| 90 |
+
ignore_index=True,
|
| 91 |
+
).set_index("episode_index")
|
| 92 |
+
for rec in self.episodes:
|
| 93 |
+
row = depth_meta.loc[rec["episode_index"]]
|
| 94 |
+
rec["depth_video"] = {}
|
| 95 |
+
rec["depth_from_ts"] = {}
|
| 96 |
+
for dst, src in DEPTH_SRCS.items():
|
| 97 |
+
rec["depth_video"][dst] = os.path.join(
|
| 98 |
+
self.root, "videos", src,
|
| 99 |
+
f"chunk-{int(row[f'videos/{src}/chunk_index']):03d}",
|
| 100 |
+
f"file-{int(row[f'videos/{src}/file_index']):03d}.mp4",
|
| 101 |
+
)
|
| 102 |
+
rec["depth_from_ts"][dst] = float(row[f"videos/{src}/from_timestamp"])
|
| 103 |
+
if not os.path.exists(rec["depth_video"][dst]):
|
| 104 |
+
raise FileNotFoundError(
|
| 105 |
+
f"GT depth requested but missing: {rec['depth_video'][dst]}"
|
| 106 |
+
)
|
| 107 |
+
logger.info("GT metric depth enabled for %d episodes", len(self.episodes))
|
| 108 |
+
|
| 109 |
+
def _episode_poses(self, rec):
|
| 110 |
+
"""Cached per-episode pose table (the base reader's parquet cache omits pose columns)."""
|
| 111 |
+
import pandas as pd
|
| 112 |
+
if not hasattr(self, "_pose_cache"):
|
| 113 |
+
from collections import OrderedDict
|
| 114 |
+
self._pose_cache = OrderedDict()
|
| 115 |
+
key = rec["data"]
|
| 116 |
+
if key not in self._pose_cache:
|
| 117 |
+
df = pd.read_parquet(key, columns=["episode_index", "frame_index", *POSE_COLS.values()])
|
| 118 |
+
self._pose_cache[key] = df
|
| 119 |
+
if len(self._pose_cache) > 8:
|
| 120 |
+
self._pose_cache.popitem(last=False)
|
| 121 |
+
df = self._pose_cache[key]
|
| 122 |
+
return df[df["episode_index"] == rec["episode_index"]].sort_values("frame_index")
|
| 123 |
+
|
| 124 |
+
def _decode_native(self, path: str, ts: float):
|
| 125 |
+
"""Decode ONE frame at NATIVE resolution, cached by (path, frame_idx). Returns (HWC uint8, native_w).
|
| 126 |
+
Shared by the base (224) and DA3 (252) decode so each frame is decoded ONCE, not twice.
|
| 127 |
+
Enabled only when B1K_SHARED_DECODE=1 (default off => original two-decode behavior)."""
|
| 128 |
+
if not hasattr(self, "_native_cache"):
|
| 129 |
+
from collections import OrderedDict
|
| 130 |
+
self._native_cache = OrderedDict()
|
| 131 |
+
fidx = int(round(ts * self.fps))
|
| 132 |
+
key = (path, fidx)
|
| 133 |
+
hit = self._native_cache.get(key)
|
| 134 |
+
if hit is not None:
|
| 135 |
+
self._native_cache.move_to_end(key)
|
| 136 |
+
return hit
|
| 137 |
+
container = self._cached_container(path)
|
| 138 |
+
vs = container.streams.video[0]
|
| 139 |
+
container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
|
| 140 |
+
frame = None
|
| 141 |
+
for fr in container.decode(vs):
|
| 142 |
+
if fr.time is not None and fr.time >= ts - 1e-3:
|
| 143 |
+
frame = fr
|
| 144 |
+
break
|
| 145 |
+
if frame is None:
|
| 146 |
+
container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
|
| 147 |
+
for fr in container.decode(vs):
|
| 148 |
+
frame = fr
|
| 149 |
+
out = (frame.to_ndarray(format="rgb24"), int(frame.width)) # native HWC uint8, no resize
|
| 150 |
+
self._native_cache[key] = out
|
| 151 |
+
if len(self._native_cache) > 12: # a few cams x a couple frames in flight
|
| 152 |
+
self._native_cache.popitem(last=False)
|
| 153 |
+
return out
|
| 154 |
+
|
| 155 |
+
def _decode_rgb(self, path: str, ts: float) -> np.ndarray:
|
| 156 |
+
"""Base VLM (224) frame. With shared-decode, resize from the single native decode (no 2nd decode)."""
|
| 157 |
+
if os.environ.get("B1K_SHARED_DECODE") != "1":
|
| 158 |
+
return super()._decode_rgb(path, ts)
|
| 159 |
+
import cv2
|
| 160 |
+
native, _ = self._decode_native(path, ts)
|
| 161 |
+
r = self._decode_resize
|
| 162 |
+
return cv2.resize(native, (r, r), interpolation=cv2.INTER_AREA) if r > 0 else native
|
| 163 |
+
|
| 164 |
+
def _decode_da3(self, path: str, ts: float) -> np.ndarray:
|
| 165 |
+
"""Decode one frame at DA3 resolution (252). HWC uint8 + native width (for intrinsics)."""
|
| 166 |
+
if os.environ.get("B1K_SHARED_DECODE") == "1":
|
| 167 |
+
import cv2
|
| 168 |
+
native, native_w = self._decode_native(path, ts) # reuses the base decode (no 2nd decode)
|
| 169 |
+
h, w = self._da3_hw
|
| 170 |
+
return cv2.resize(native, (w, h), interpolation=cv2.INTER_AREA), native_w
|
| 171 |
+
container = self._cached_container(path)
|
| 172 |
+
vs = container.streams.video[0]
|
| 173 |
+
container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
|
| 174 |
+
frame = None
|
| 175 |
+
for fr in container.decode(vs):
|
| 176 |
+
if fr.time is not None and fr.time >= ts - 1e-3:
|
| 177 |
+
frame = fr
|
| 178 |
+
break
|
| 179 |
+
if frame is None:
|
| 180 |
+
container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
|
| 181 |
+
for fr in container.decode(vs):
|
| 182 |
+
frame = fr
|
| 183 |
+
h, w = self._da3_hw
|
| 184 |
+
native_w = frame.width
|
| 185 |
+
frame = frame.reformat(width=w, height=h, format="rgb24")
|
| 186 |
+
img = frame.to_ndarray(format="rgb24")
|
| 187 |
+
return img, native_w
|
| 188 |
+
|
| 189 |
+
def _decode_gt_depth(self, path: str, ts: float, grid_hw: tuple[int, int]) -> np.ndarray:
|
| 190 |
+
"""Decode BEHAVIOR-1K gray12le linear depth and align it to the DA3 patch grid.
|
| 191 |
+
|
| 192 |
+
Native gray12le samples are metric millimeters (not gray16-scaled values). The depth and
|
| 193 |
+
RGB videos share the same square camera raster/FOV; resizing native depth directly to the
|
| 194 |
+
(H/14,W/14) token grid therefore aligns each depth cell with the corresponding DA3 patch.
|
| 195 |
+
Area resampling averages metric Z-depth over the same image support represented by a token.
|
| 196 |
+
"""
|
| 197 |
+
import cv2
|
| 198 |
+
|
| 199 |
+
container = self._cached_container(path)
|
| 200 |
+
vs = container.streams.video[0]
|
| 201 |
+
container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
|
| 202 |
+
frame = None
|
| 203 |
+
for fr in container.decode(vs):
|
| 204 |
+
if fr.time is not None and fr.time >= ts - 1e-3:
|
| 205 |
+
frame = fr
|
| 206 |
+
break
|
| 207 |
+
if frame is None:
|
| 208 |
+
container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
|
| 209 |
+
for fr in container.decode(vs):
|
| 210 |
+
frame = fr
|
| 211 |
+
# Do not request gray16le: swscale left-shifts the native 12-bit samples by four bits.
|
| 212 |
+
depth_m = frame.to_ndarray().astype(np.float32) / 1000.0
|
| 213 |
+
gh, gw = grid_hw
|
| 214 |
+
depth_grid = cv2.resize(depth_m, (gw, gh), interpolation=cv2.INTER_AREA)
|
| 215 |
+
return depth_grid[None] # [1,gh,gw]
|
| 216 |
+
|
| 217 |
+
def _lang_entry(self, task_name: str):
|
| 218 |
+
if self._lang is None:
|
| 219 |
+
L = self._lang_max_len
|
| 220 |
+
return np.zeros((L, 1024), np.float32), np.zeros((L,), bool)
|
| 221 |
+
feat, mask = self._lang[task_name]
|
| 222 |
+
return np.asarray(feat, np.float32), np.asarray(mask, bool)
|
| 223 |
+
|
| 224 |
+
def da3_fields(self, i):
|
| 225 |
+
"""Compute ONLY the DA3 input fields for sample i (attached AFTER the transform stack,
|
| 226 |
+
which constructs fresh dicts and would drop unknown keys)."""
|
| 227 |
+
item = {}
|
| 228 |
+
ei, t = self.samples[i]
|
| 229 |
+
rec = self.episodes[ei]
|
| 230 |
+
sub = self._episode_poses(rec)
|
| 231 |
+
|
| 232 |
+
h, w = self._da3_hw
|
| 233 |
+
imgs, extr, intr, gt_depth = [], [], [], []
|
| 234 |
+
use_gt_depth = os.environ.get("B1K_USE_GT_DEPTH") == "1"
|
| 235 |
+
grid_hw = (h // 14, w // 14)
|
| 236 |
+
for dst in VIEW_ORDER:
|
| 237 |
+
frame_ts = rec["from_ts"][dst] + t / self.fps
|
| 238 |
+
img, native_w = self._decode_da3(rec["video"][dst], frame_ts)
|
| 239 |
+
imgs.append(img)
|
| 240 |
+
p7 = np.asarray(sub[POSE_COLS[dst]].iloc[t], np.float64)
|
| 241 |
+
extr.append(pose7_to_robot2cam(p7))
|
| 242 |
+
f_native = FOCAL_RATIO * native_w
|
| 243 |
+
# native (square) -> da3_hw rescale: fx,cx scale by w/native_w; fy,cy by h/native_h(=native_w)
|
| 244 |
+
K = np.array([
|
| 245 |
+
[f_native * w / native_w, 0, (native_w / 2) * w / native_w],
|
| 246 |
+
[0, f_native * h / native_w, (native_w / 2) * h / native_w],
|
| 247 |
+
[0, 0, 1],
|
| 248 |
+
], np.float32)
|
| 249 |
+
intr.append(K)
|
| 250 |
+
if use_gt_depth:
|
| 251 |
+
depth_ts = rec["depth_from_ts"][dst] + t / self.fps
|
| 252 |
+
gt_depth.append(self._decode_gt_depth(
|
| 253 |
+
rec["depth_video"][dst], depth_ts, grid_hw
|
| 254 |
+
))
|
| 255 |
+
|
| 256 |
+
item["da3_images"] = np.stack(imgs, 0) # [V,252,252,3] uint8
|
| 257 |
+
item["camera_extrinsics"] = np.stack(extr, 0) # [V,4,4] robot->cam OpenCV
|
| 258 |
+
item["camera_intrinsics"] = np.stack(intr, 0) # [V,3,3] @ da3_hw
|
| 259 |
+
if use_gt_depth:
|
| 260 |
+
item["gt_metric_depth"] = np.stack(gt_depth, 0).astype(np.float32) # [V,1,gh,gw], meters
|
| 261 |
+
lf, lm = self._lang_entry(rec["task0"])
|
| 262 |
+
item["lang_feat"] = lf
|
| 263 |
+
item["lang_mask"] = lm
|
| 264 |
+
return item
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
class _AttachDA3Fields:
|
| 268 |
+
"""Wraps the TRANSFORMED dataset; merges the raw dataset's DA3 fields into each sample."""
|
| 269 |
+
|
| 270 |
+
def __init__(self, transformed, raw: BehaviorV3DA3Dataset):
|
| 271 |
+
self._transformed = transformed
|
| 272 |
+
self._raw = raw
|
| 273 |
+
|
| 274 |
+
def __len__(self):
|
| 275 |
+
return len(self._transformed)
|
| 276 |
+
|
| 277 |
+
def __getitem__(self, i):
|
| 278 |
+
out = dict(self._transformed[i])
|
| 279 |
+
out.update(self._raw.da3_fields(i))
|
| 280 |
+
return out
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def create_v3_behavior_da3_loader(config, root_2026, activities, task_data_json, *,
|
| 284 |
+
lang_cache, sharding=None, shuffle=True,
|
| 285 |
+
num_workers=None, seed=0, da3_hw=(252, 252)):
|
| 286 |
+
"""v3 loader with DA3 inputs + a per-batch frozen DA3-GIANT extraction hook (GPU)."""
|
| 287 |
+
import jax
|
| 288 |
+
import dataclasses as _dc
|
| 289 |
+
from b1k.policies import b1k_policy
|
| 290 |
+
from b1k.training.data_loader import transform_dataset, DataLoaderImpl
|
| 291 |
+
from openpi.training.data_loader import TorchDataLoader
|
| 292 |
+
from b1k.training import da3_extractor as _ex
|
| 293 |
+
|
| 294 |
+
data_config = config.data.create(config.assets_dirs, config.model)
|
| 295 |
+
new_inputs = tuple(
|
| 296 |
+
B1kInputs2026(model_type=config.model.model_type)
|
| 297 |
+
if isinstance(x, b1k_policy.B1kInputs) else x
|
| 298 |
+
for x in data_config.data_transforms.inputs
|
| 299 |
+
)
|
| 300 |
+
data_config = _dc.replace(
|
| 301 |
+
data_config, data_transforms=_dc.replace(data_config.data_transforms, inputs=new_inputs))
|
| 302 |
+
|
| 303 |
+
# VGGT-Omega extractor: decode/patchify at process_res (patch 16), not the DA3 252 grid.
|
| 304 |
+
_use_vggt = os.environ.get("USE_VGGT") == "1"
|
| 305 |
+
if _use_vggt:
|
| 306 |
+
da3_hw = (int(os.environ.get("VGGT_PROCESS_RES", "256")),) * 2
|
| 307 |
+
|
| 308 |
+
ds = BehaviorV3DA3Dataset(
|
| 309 |
+
root_2026, activities=activities, action_horizon=config.model.action_horizon,
|
| 310 |
+
task_data_json=task_data_json, seed=seed,
|
| 311 |
+
da3_hw=da3_hw, lang_cache=lang_cache,
|
| 312 |
+
lang_max_len=config.model.da3.lang_max_len,
|
| 313 |
+
)
|
| 314 |
+
tds = transform_dataset(ds, data_config)
|
| 315 |
+
tds = _AttachDA3Fields(tds, ds)
|
| 316 |
+
|
| 317 |
+
logger.info("Building inline DA3-GIANT extractor (da3_hw=%s) ...", da3_hw)
|
| 318 |
+
# Extraction devices: default single-GPU (cuda:0). Set B1K_EXTRACT_DEVICES to spread the frozen
|
| 319 |
+
# DA3-GIANT forward across GPUs (one replica per device, batch split, run concurrently) so the
|
| 320 |
+
# ~2.7s single-GPU extraction shrinks and better overlaps the JAX train step.
|
| 321 |
+
_dev_env = os.environ.get("B1K_EXTRACT_DEVICES", "").strip()
|
| 322 |
+
_devices = [d.strip() for d in _dev_env.split(",") if d.strip()] or None
|
| 323 |
+
_fchunk = int(os.environ.get("B1K_DA3_FWD_CHUNK", "16"))
|
| 324 |
+
logger.info("DA3 extractor: devices=%s forward_chunk=%d", _devices or ["cuda:0"], _fchunk)
|
| 325 |
+
if _use_vggt:
|
| 326 |
+
from b1k.training import vggt_extractor as _vex
|
| 327 |
+
extractor = _vex.VGGTInlineExtractor(process_res=da3_hw[0], forward_chunk=_fchunk, devices=_devices)
|
| 328 |
+
logger.info("Using VGGT-Omega extractor (process_res=%d, grid=%d)", da3_hw[0], da3_hw[0] // 16)
|
| 329 |
+
else:
|
| 330 |
+
model_name = os.environ.get(
|
| 331 |
+
"DA3_MODEL_NAME", "depth-anything/DA3NESTED-GIANT-LARGE-1.1"
|
| 332 |
+
)
|
| 333 |
+
out_layers = tuple(int(x) for x in os.environ.get(
|
| 334 |
+
"DA3_OUT_LAYERS", "19,26,33,39"
|
| 335 |
+
).split(","))
|
| 336 |
+
extractor = _ex.DA3InlineExtractor(
|
| 337 |
+
model_name=model_name, out_layers=out_layers, da3_hw=da3_hw,
|
| 338 |
+
forward_chunk=_fchunk, devices=_devices,
|
| 339 |
+
)
|
| 340 |
+
logger.info("Using DA3 extractor model=%s out_layers=%s", model_name, out_layers)
|
| 341 |
+
|
| 342 |
+
# DLPack GPU->GPU handoff: skip the ~2.6s/batch host round-trip by moving extractor features
|
| 343 |
+
# straight from the extraction GPUs to the training GPUs over NVLink. Requires CUDA extraction
|
| 344 |
+
# devices whose count matches the training mesh size (contiguous batch split aligns 1:1).
|
| 345 |
+
# Holds the last few batches' torch source shards alive so the async NVLink copies (device_put)
|
| 346 |
+
# can never read freed memory — replaces a blocking block_until_ready that serialized the producer.
|
| 347 |
+
import collections as _collections
|
| 348 |
+
_keepalive = _collections.deque(maxlen=4)
|
| 349 |
+
|
| 350 |
+
def _dlpack_ok():
|
| 351 |
+
try:
|
| 352 |
+
m = getattr(sharding, "mesh", None)
|
| 353 |
+
return (os.environ.get("B1K_DLPACK") == "1" and m is not None
|
| 354 |
+
and len(list(m.devices.flat)) == len(extractor.devices)
|
| 355 |
+
and all(str(d).startswith("cuda") for d in extractor.devices))
|
| 356 |
+
except Exception:
|
| 357 |
+
return False
|
| 358 |
+
|
| 359 |
+
# Output field order MUST match the extractor's extract()/extract_shards_torch() tuple order.
|
| 360 |
+
_field_names = (["da3_features", "da3_ray", "da3_depth",
|
| 361 |
+
"da3_depth_conf", "da3_pose_enc", "da3_cam_tokens"] if _use_vggt
|
| 362 |
+
else ["da3_features", "da3_ray", "da3_depth"])
|
| 363 |
+
|
| 364 |
+
def batch_transform(batch):
|
| 365 |
+
use_gt_depth = os.environ.get("B1K_USE_GT_DEPTH") == "1"
|
| 366 |
+
if _dlpack_ok():
|
| 367 |
+
import torch
|
| 368 |
+
import jax
|
| 369 |
+
parts = extractor.extract_shards_torch(
|
| 370 |
+
batch["da3_images"], batch["camera_extrinsics"], batch["camera_intrinsics"])
|
| 371 |
+
tdevs = list(sharding.mesh.devices.flat) # training devices, batch-chunk k -> tdevs[k]
|
| 372 |
+
if use_gt_depth:
|
| 373 |
+
gt = batch.pop("gt_metric_depth")
|
| 374 |
+
bounds = [round(i * int(gt.shape[0]) / len(parts)) for i in range(len(parts) + 1)]
|
| 375 |
+
replaced = []
|
| 376 |
+
for k, part in enumerate(parts):
|
| 377 |
+
d = gt[bounds[k]:bounds[k + 1]]
|
| 378 |
+
if not isinstance(d, torch.Tensor):
|
| 379 |
+
d = torch.as_tensor(d)
|
| 380 |
+
d = d.to(extractor.devices[k], dtype=torch.float32, non_blocking=True)
|
| 381 |
+
replaced.append((part[0], part[1], d))
|
| 382 |
+
parts = replaced
|
| 383 |
+
|
| 384 |
+
def _asm(fi): # assemble per-shard torch tensors (field fi) into one sharded jax array
|
| 385 |
+
js = [jax.device_put(jax.dlpack.from_dlpack(parts[k][fi]), tdevs[k]) for k in range(len(parts))]
|
| 386 |
+
gshape = (sum(int(s.shape[0]) for s in js),) + tuple(int(d) for d in js[0].shape[1:])
|
| 387 |
+
return jax.make_array_from_single_device_arrays(gshape, sharding, js)
|
| 388 |
+
|
| 389 |
+
for fi, nm in enumerate(_field_names):
|
| 390 |
+
batch[nm] = _asm(fi)
|
| 391 |
+
# Do NOT block here: the device_put queues behind the in-flight train step on the target
|
| 392 |
+
# GPUs, so blocking would serialize the producer with training (killing the overlap).
|
| 393 |
+
# Instead keep the torch source shards referenced for a few batches so the async NVLink
|
| 394 |
+
# copy can't read freed memory.
|
| 395 |
+
_keepalive.append(parts)
|
| 396 |
+
else:
|
| 397 |
+
outs = extractor.extract(
|
| 398 |
+
batch["da3_images"], batch["camera_extrinsics"], batch["camera_intrinsics"])
|
| 399 |
+
if use_gt_depth:
|
| 400 |
+
gt = batch.pop("gt_metric_depth")
|
| 401 |
+
if hasattr(gt, "detach"):
|
| 402 |
+
gt = gt.detach().cpu().numpy()
|
| 403 |
+
outs = (outs[0], outs[1], np.asarray(gt, dtype=np.float32))
|
| 404 |
+
for nm, arr in zip(_field_names, outs):
|
| 405 |
+
batch[nm] = arr # da3_features = uint16 bf16-bits; rest fp32
|
| 406 |
+
batch.pop("da3_images", None)
|
| 407 |
+
batch.pop("camera_intrinsics", None)
|
| 408 |
+
return batch
|
| 409 |
+
|
| 410 |
+
loader = TorchDataLoader(
|
| 411 |
+
tds, local_batch_size=config.batch_size // jax.process_count(),
|
| 412 |
+
sharding=sharding, shuffle=shuffle,
|
| 413 |
+
num_workers=config.num_workers if num_workers is None else num_workers,
|
| 414 |
+
seed=seed, batch_transform=batch_transform,
|
| 415 |
+
)
|
| 416 |
+
return DataLoaderImpl(data_config, loader)
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/config.py
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Training configuration for BEHAVIOR-1K challenge.
|
| 2 |
+
|
| 3 |
+
Reference: https://github.com/Physical-Intelligence/openpi
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import abc
|
| 7 |
+
from collections.abc import Sequence
|
| 8 |
+
import dataclasses
|
| 9 |
+
import difflib
|
| 10 |
+
import logging
|
| 11 |
+
import os
|
| 12 |
+
import pathlib
|
| 13 |
+
from typing import Any, Literal, List, Protocol, TypeAlias
|
| 14 |
+
|
| 15 |
+
import etils.epath as epath
|
| 16 |
+
import flax.nnx as nnx
|
| 17 |
+
from typing_extensions import override
|
| 18 |
+
import tyro
|
| 19 |
+
|
| 20 |
+
# Import from OpenPI
|
| 21 |
+
import openpi.models.model as _model
|
| 22 |
+
import openpi.models.pi0_config as pi0_config
|
| 23 |
+
import openpi.policies.aloha_policy as aloha_policy
|
| 24 |
+
import openpi.policies.droid_policy as droid_policy
|
| 25 |
+
import openpi.policies.libero_policy as libero_policy
|
| 26 |
+
import openpi.shared.download as _download
|
| 27 |
+
import openpi.training.droid_rlds_dataset as droid_rlds_dataset
|
| 28 |
+
import openpi.training.optimizer as _optimizer
|
| 29 |
+
import openpi.transforms as _transforms
|
| 30 |
+
|
| 31 |
+
# Import from B1K custom modules
|
| 32 |
+
from b1k.models import pi_behavior_config
|
| 33 |
+
from b1k.policies import b1k_policy
|
| 34 |
+
from b1k.shared import normalize as _normalize
|
| 35 |
+
from b1k.training import weight_loaders
|
| 36 |
+
from b1k import transforms as b1k_transforms
|
| 37 |
+
|
| 38 |
+
ModelType: TypeAlias = _model.ModelType
|
| 39 |
+
Filter: TypeAlias = nnx.filterlib.Filter
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@dataclasses.dataclass(frozen=True)
|
| 43 |
+
class AssetsConfig:
|
| 44 |
+
"""Determines the location of assets (e.g., norm stats) that will be used to set up the data pipeline.
|
| 45 |
+
|
| 46 |
+
These assets will be replicated inside the checkpoint under the `assets/asset_id` directory.
|
| 47 |
+
"""
|
| 48 |
+
# Assets directory. If not provided, the config assets_dirs will be used.
|
| 49 |
+
assets_dir: str | None = None
|
| 50 |
+
# Asset id. If not provided, the repo id will be used.
|
| 51 |
+
asset_id: str | None = None
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@dataclasses.dataclass(frozen=True)
|
| 55 |
+
class DataConfig:
|
| 56 |
+
# LeRobot repo id. If None, fake data will be created.
|
| 57 |
+
repo_id: str | None = None
|
| 58 |
+
# Directory within the assets directory containing the data assets.
|
| 59 |
+
asset_id: str | None = None
|
| 60 |
+
# Contains precomputed normalization stats. If None, normalization will not be performed.
|
| 61 |
+
norm_stats: dict[str, _transforms.NormStats] | None = None
|
| 62 |
+
|
| 63 |
+
# Used to adopt the inputs from a dataset specific format to a common format
|
| 64 |
+
repack_transforms: _transforms.Group = dataclasses.field(default_factory=_transforms.Group)
|
| 65 |
+
# Data transforms, typically include robot specific transformations.
|
| 66 |
+
data_transforms: _transforms.Group = dataclasses.field(default_factory=_transforms.Group)
|
| 67 |
+
# Model specific transforms. Will be applied after the data is normalized.
|
| 68 |
+
model_transforms: _transforms.Group = dataclasses.field(default_factory=_transforms.Group)
|
| 69 |
+
|
| 70 |
+
# If true, will use quantile normalization. Otherwise, normal z-score normalization will be used.
|
| 71 |
+
use_quantile_norm: bool = False
|
| 72 |
+
# If true, will use per-timestamp normalization for actions
|
| 73 |
+
use_per_timestamp_norm: bool = False
|
| 74 |
+
|
| 75 |
+
# Names of keys that will be used by the data loader to generate the action sequence.
|
| 76 |
+
action_sequence_keys: Sequence[str] = ("actions",)
|
| 77 |
+
|
| 78 |
+
# If true, will use the LeRobot dataset task to define the prompt (not used for PI_BEHAVIOR).
|
| 79 |
+
prompt_from_task: bool = False
|
| 80 |
+
|
| 81 |
+
# Only used for RLDS data loader.
|
| 82 |
+
rlds_data_dir: str | None = None
|
| 83 |
+
|
| 84 |
+
# Only used for B1K data loader.
|
| 85 |
+
behavior_dataset_root: str | None = None
|
| 86 |
+
|
| 87 |
+
# Action space for DROID dataset.
|
| 88 |
+
action_space: droid_rlds_dataset.DroidActionSpace | None = None
|
| 89 |
+
# Path to the data filter file for DROID dataset
|
| 90 |
+
filter_dict_path: str | None = None
|
| 91 |
+
|
| 92 |
+
# Episodes index to use for training
|
| 93 |
+
episodes_index: List[int] | None = None
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class GroupFactory(Protocol):
|
| 97 |
+
def __call__(self, model_config: _model.BaseModelConfig) -> _transforms.Group:
|
| 98 |
+
"""Create a group."""
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
@dataclasses.dataclass(frozen=True)
|
| 102 |
+
class ModelTransformFactory(GroupFactory):
|
| 103 |
+
"""Creates model transforms for B1K."""
|
| 104 |
+
|
| 105 |
+
default_prompt: str | None = None # Not used (task embeddings instead)
|
| 106 |
+
|
| 107 |
+
def __call__(self, model_config: _model.BaseModelConfig) -> _transforms.Group:
|
| 108 |
+
return _transforms.Group(
|
| 109 |
+
inputs=[
|
| 110 |
+
_transforms.ResizeImages(224, 224),
|
| 111 |
+
b1k_transforms.ComputeSubtaskStateFromMeta(dataset=None),
|
| 112 |
+
b1k_transforms.TaskIndexToTaskId(),
|
| 113 |
+
_transforms.PadStatesAndActions(model_config.action_dim),
|
| 114 |
+
],
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
@dataclasses.dataclass(frozen=True)
|
| 119 |
+
class DataConfigFactory(abc.ABC):
|
| 120 |
+
# The LeRobot repo id.
|
| 121 |
+
repo_id: str = tyro.MISSING
|
| 122 |
+
# Determines how the assets will be loaded.
|
| 123 |
+
assets: AssetsConfig = dataclasses.field(default_factory=AssetsConfig)
|
| 124 |
+
# Base config that will be updated by the factory.
|
| 125 |
+
base_config: tyro.conf.Suppress[DataConfig | None] = None
|
| 126 |
+
|
| 127 |
+
@abc.abstractmethod
|
| 128 |
+
def create(self, assets_dirs: pathlib.Path, model_config: _model.BaseModelConfig) -> DataConfig:
|
| 129 |
+
"""Create a data config."""
|
| 130 |
+
|
| 131 |
+
def create_base_config(self, assets_dirs: pathlib.Path, model_config: _model.BaseModelConfig) -> DataConfig:
|
| 132 |
+
repo_id = self.repo_id if self.repo_id is not tyro.MISSING else None
|
| 133 |
+
asset_id = self.assets.asset_id or repo_id
|
| 134 |
+
return dataclasses.replace(
|
| 135 |
+
self.base_config or DataConfig(),
|
| 136 |
+
repo_id=repo_id,
|
| 137 |
+
asset_id=asset_id,
|
| 138 |
+
norm_stats=self._load_norm_stats(epath.Path(self.assets.assets_dir or assets_dirs), asset_id),
|
| 139 |
+
use_quantile_norm=False, # Always use z-score normalization for B1K
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
def _load_norm_stats(self, assets_dir: epath.Path, asset_id: str | None) -> dict[str, _transforms.NormStats] | None:
|
| 143 |
+
if asset_id is None:
|
| 144 |
+
return None
|
| 145 |
+
try:
|
| 146 |
+
data_assets_dir = str(assets_dir / asset_id)
|
| 147 |
+
norm_stats = _normalize.load(_download.maybe_download(data_assets_dir))
|
| 148 |
+
logging.info(f"Loaded norm stats from {data_assets_dir}")
|
| 149 |
+
return norm_stats
|
| 150 |
+
except FileNotFoundError:
|
| 151 |
+
logging.info(f"Norm stats not found in {data_assets_dir}, skipping.")
|
| 152 |
+
return None
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
@dataclasses.dataclass(frozen=True)
|
| 156 |
+
class LeRobotB1KDataConfig(DataConfigFactory):
|
| 157 |
+
"""Data configuration for BEHAVIOR-1K dataset."""
|
| 158 |
+
|
| 159 |
+
action_sequence_keys: Sequence[str] = ("action",)
|
| 160 |
+
use_delta_joint_actions: bool = False
|
| 161 |
+
|
| 162 |
+
# FAST auxiliary tokenization (only for PI_BEHAVIOR with use_fast_auxiliary)
|
| 163 |
+
use_fast_tokenization: bool = False
|
| 164 |
+
|
| 165 |
+
@override
|
| 166 |
+
def create(self, assets_dirs: pathlib.Path, model_config: _model.BaseModelConfig) -> DataConfig:
|
| 167 |
+
# Repack transforms for B1K observations
|
| 168 |
+
repack_mapping = {
|
| 169 |
+
"observation/egocentric_camera": "observation.images.rgb.head",
|
| 170 |
+
"observation/wrist_image_left": "observation.images.rgb.left_wrist",
|
| 171 |
+
"observation/wrist_image_right": "observation.images.rgb.right_wrist",
|
| 172 |
+
"observation/state": "observation.state",
|
| 173 |
+
"actions": "action",
|
| 174 |
+
"task_index": "task_index", # Always preserve task_index
|
| 175 |
+
"timestamp": "timestamp", # Preserve timestamp for subtask state computation
|
| 176 |
+
"episode_index": "episode_index", # Preserve episode_index for episode length lookup
|
| 177 |
+
"index": "index", # Preserve index
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
repack_transform = _transforms.Group(
|
| 181 |
+
inputs=[_transforms.RepackTransform(repack_mapping)]
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
# Prepare data for policy training
|
| 185 |
+
data_transforms = _transforms.Group(
|
| 186 |
+
inputs=[b1k_policy.B1kInputs(model_type=model_config.model_type)],
|
| 187 |
+
outputs=[b1k_policy.B1kOutputs()],
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
# Delta action transforms
|
| 191 |
+
if self.use_delta_joint_actions:
|
| 192 |
+
delta_action_mask = _transforms.make_bool_mask(-3, 3, -1, 7, -1, 7, -1)
|
| 193 |
+
else:
|
| 194 |
+
delta_action_mask = _transforms.make_bool_mask(-23)
|
| 195 |
+
|
| 196 |
+
data_transforms = data_transforms.push(
|
| 197 |
+
inputs=[_transforms.DeltaActions(delta_action_mask)],
|
| 198 |
+
outputs=[_transforms.AbsoluteActions(delta_action_mask)],
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
# Model transforms (subtask state, task ID, padding)
|
| 202 |
+
model_transforms = ModelTransformFactory()(model_config)
|
| 203 |
+
|
| 204 |
+
# FAST tokenization (if enabled for PI_BEHAVIOR)
|
| 205 |
+
if self.use_fast_tokenization and hasattr(model_config, 'use_fast_auxiliary') and model_config.use_fast_auxiliary:
|
| 206 |
+
asset_id = self.assets.asset_id or self.repo_id
|
| 207 |
+
tokenizer_path = assets_dirs / asset_id / "fast_tokenizer"
|
| 208 |
+
|
| 209 |
+
# Get base config to access norm_stats
|
| 210 |
+
base_config = self.create_base_config(assets_dirs, model_config)
|
| 211 |
+
|
| 212 |
+
# Only add transform if tokenizer directory exists
|
| 213 |
+
if tokenizer_path.exists():
|
| 214 |
+
model_transforms = model_transforms.push(
|
| 215 |
+
inputs=[b1k_transforms.TokenizeFASTActions(
|
| 216 |
+
tokenizer_path=str(tokenizer_path),
|
| 217 |
+
encoded_dim_ranges=model_config.get_fast_dim_ranges(),
|
| 218 |
+
max_fast_tokens=model_config.max_fast_tokens,
|
| 219 |
+
norm_stats=base_config.norm_stats,
|
| 220 |
+
use_per_timestamp=base_config.use_per_timestamp_norm,
|
| 221 |
+
)],
|
| 222 |
+
)
|
| 223 |
+
else:
|
| 224 |
+
logging.warning(
|
| 225 |
+
f"FAST tokenizer not found at {tokenizer_path}. "
|
| 226 |
+
"FAST auxiliary training will be disabled (inference mode)."
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
return dataclasses.replace(
|
| 230 |
+
self.create_base_config(assets_dirs, model_config),
|
| 231 |
+
repack_transforms=repack_transform,
|
| 232 |
+
data_transforms=data_transforms,
|
| 233 |
+
model_transforms=model_transforms,
|
| 234 |
+
action_sequence_keys=self.action_sequence_keys,
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
@dataclasses.dataclass(frozen=True)
|
| 239 |
+
class TrainConfig:
|
| 240 |
+
# Name of the config. Must be unique. Will be used to reference this config.
|
| 241 |
+
name: tyro.conf.Suppress[str]
|
| 242 |
+
# Project name.
|
| 243 |
+
project_name: str = "B1K"
|
| 244 |
+
# Experiment name. Will be used to name the metadata and checkpoint directories.
|
| 245 |
+
exp_name: str = tyro.MISSING
|
| 246 |
+
|
| 247 |
+
# Defines the model config (PI_BEHAVIOR only for B1K).
|
| 248 |
+
model: _model.BaseModelConfig = dataclasses.field(default_factory=pi_behavior_config.PiBehaviorConfig)
|
| 249 |
+
|
| 250 |
+
# A weight loader can optionally load (possibly partial) weights from disk after the model is initialized.
|
| 251 |
+
weight_loader: weight_loaders.WeightLoader = dataclasses.field(default_factory=weight_loaders.NoOpWeightLoader)
|
| 252 |
+
|
| 253 |
+
# Note: PyTorch support removed - JAX only
|
| 254 |
+
|
| 255 |
+
lr_schedule: _optimizer.LRScheduleConfig = dataclasses.field(default_factory=_optimizer.CosineDecaySchedule)
|
| 256 |
+
optimizer: _optimizer.OptimizerConfig = dataclasses.field(default_factory=_optimizer.AdamW)
|
| 257 |
+
ema_decay: float | None = 0.99
|
| 258 |
+
|
| 259 |
+
# Specifies which weights should be frozen.
|
| 260 |
+
freeze_filter: tyro.conf.Suppress[Filter] = dataclasses.field(default_factory=nnx.Nothing)
|
| 261 |
+
|
| 262 |
+
# Determines the data to be trained on.
|
| 263 |
+
data: DataConfigFactory = dataclasses.field(default_factory=LeRobotB1KDataConfig)
|
| 264 |
+
|
| 265 |
+
# Base directory for config assets (e.g., norm stats).
|
| 266 |
+
assets_base_dir: str = "./assets"
|
| 267 |
+
# Base directory for checkpoints.
|
| 268 |
+
checkpoint_base_dir: str = "./checkpoints"
|
| 269 |
+
|
| 270 |
+
# Random seed that will be used by random generators during training.
|
| 271 |
+
seed: int | None = None
|
| 272 |
+
# Global batch size.
|
| 273 |
+
batch_size: int = 32
|
| 274 |
+
# Number of workers to use for the data loader.
|
| 275 |
+
num_workers: int = 2
|
| 276 |
+
# Number of train steps (batches) to run.
|
| 277 |
+
num_train_steps: int = 30_000
|
| 278 |
+
|
| 279 |
+
# How often (in steps) to log training metrics.
|
| 280 |
+
log_interval: int = 100
|
| 281 |
+
# How often (in steps) to save checkpoints.
|
| 282 |
+
save_interval: int = 1000
|
| 283 |
+
# If set, any existing checkpoints matching step % keep_period == 0 will not be deleted.
|
| 284 |
+
keep_period: int | None = 5000
|
| 285 |
+
|
| 286 |
+
# If true, will overwrite the checkpoint directory if it already exists.
|
| 287 |
+
overwrite: bool = False
|
| 288 |
+
# If true, will resume training from the last checkpoint.
|
| 289 |
+
resume: bool = False
|
| 290 |
+
|
| 291 |
+
# If true, will enable wandb logging.
|
| 292 |
+
wandb_enabled: bool = True
|
| 293 |
+
|
| 294 |
+
# Used to pass metadata to the policy server.
|
| 295 |
+
policy_metadata: dict[str, Any] | None = None
|
| 296 |
+
|
| 297 |
+
# FSDP configuration for model sharding across devices.
|
| 298 |
+
fsdp_devices: int = 1
|
| 299 |
+
|
| 300 |
+
# Validation configuration
|
| 301 |
+
val_log_interval: int = 100
|
| 302 |
+
val_batch_size: int | None = None
|
| 303 |
+
val_num_batches: int = 10
|
| 304 |
+
val_repo_id: str | None = None
|
| 305 |
+
val_episodes_index: List[int] | None = None
|
| 306 |
+
|
| 307 |
+
# Number of flow matching samples per training step
|
| 308 |
+
num_flow_samples: int = 1
|
| 309 |
+
|
| 310 |
+
@property
|
| 311 |
+
def assets_dirs(self) -> pathlib.Path:
|
| 312 |
+
"""Get the assets directory for this config."""
|
| 313 |
+
return (pathlib.Path(self.assets_base_dir) / self.name).resolve()
|
| 314 |
+
|
| 315 |
+
@property
|
| 316 |
+
def checkpoint_dir(self) -> pathlib.Path:
|
| 317 |
+
"""Get the checkpoint directory for this config."""
|
| 318 |
+
if not self.exp_name:
|
| 319 |
+
raise ValueError("--exp_name must be set")
|
| 320 |
+
return (pathlib.Path(self.checkpoint_base_dir) / self.name / self.exp_name).resolve()
|
| 321 |
+
|
| 322 |
+
@property
|
| 323 |
+
def trainable_filter(self) -> nnx.filterlib.Filter:
|
| 324 |
+
"""Get the filter for the trainable parameters."""
|
| 325 |
+
return nnx.All(nnx.Param, nnx.Not(self.freeze_filter))
|
| 326 |
+
|
| 327 |
+
def __post_init__(self) -> None:
|
| 328 |
+
if self.resume and self.overwrite:
|
| 329 |
+
raise ValueError("Cannot resume and overwrite at the same time.")
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
# B1K Training Configurations
|
| 333 |
+
_CONFIGS = [
|
| 334 |
+
TrainConfig(
|
| 335 |
+
name="pi_behavior_b1k_fast",
|
| 336 |
+
exp_name="openpi",
|
| 337 |
+
project_name="B1K",
|
| 338 |
+
model=pi_behavior_config.PiBehaviorConfig(
|
| 339 |
+
action_horizon=30,
|
| 340 |
+
action_dim=32,
|
| 341 |
+
use_correlated_noise=True,
|
| 342 |
+
correlation_beta=0.5,
|
| 343 |
+
# FAST auxiliary training
|
| 344 |
+
use_fast_auxiliary=True,
|
| 345 |
+
fast_loss_weight=0.05,
|
| 346 |
+
fast_encoded_dims="0:6,7:23", # Encode 22 dimensions
|
| 347 |
+
fast_vocab_size=1024,
|
| 348 |
+
max_fast_tokens=200,
|
| 349 |
+
use_kv_transform=True,
|
| 350 |
+
use_knowledge_insulation=False,
|
| 351 |
+
subtask_loss_weight=0.1,
|
| 352 |
+
freeze_vision_backbone=True,
|
| 353 |
+
),
|
| 354 |
+
data=LeRobotB1KDataConfig(
|
| 355 |
+
repo_id="IliaLarchenko/behavior_224_rgb",
|
| 356 |
+
base_config=DataConfig(
|
| 357 |
+
prompt_from_task=False, # No text prompts for PI_BEHAVIOR
|
| 358 |
+
behavior_dataset_root="~/data/behavior_224_rgb",
|
| 359 |
+
use_per_timestamp_norm=True, # Enable per-timestamp normalization
|
| 360 |
+
),
|
| 361 |
+
use_delta_joint_actions=True,
|
| 362 |
+
use_fast_tokenization=True, # Enable FAST tokenization in data pipeline
|
| 363 |
+
),
|
| 364 |
+
lr_schedule=_optimizer.CosineDecaySchedule(
|
| 365 |
+
warmup_steps=1000,
|
| 366 |
+
peak_lr=1e-4,
|
| 367 |
+
decay_steps=20_000,
|
| 368 |
+
decay_lr=1e-5,
|
| 369 |
+
),
|
| 370 |
+
num_flow_samples=15,
|
| 371 |
+
weight_loader=weight_loaders.PiBehaviorWeightLoader("gs://openpi-assets/checkpoints/pi05_base/params"),
|
| 372 |
+
num_train_steps=200_000,
|
| 373 |
+
assets_base_dir="./outputs/assets",
|
| 374 |
+
checkpoint_base_dir="./outputs/checkpoints",
|
| 375 |
+
num_workers=80,
|
| 376 |
+
save_interval=500,
|
| 377 |
+
keep_period=2000,
|
| 378 |
+
),
|
| 379 |
+
TrainConfig(
|
| 380 |
+
name="pi_behavior_b1k_stage_only",
|
| 381 |
+
exp_name="stage_only",
|
| 382 |
+
project_name="B1K",
|
| 383 |
+
model=pi_behavior_config.PiBehaviorConfig(
|
| 384 |
+
action_horizon=30,
|
| 385 |
+
action_dim=32,
|
| 386 |
+
use_correlated_noise=False,
|
| 387 |
+
correlation_beta=0.5,
|
| 388 |
+
# Keep only the task/stage conditioning path on top of pi0.5.
|
| 389 |
+
use_fast_auxiliary=False,
|
| 390 |
+
use_kv_transform=False,
|
| 391 |
+
use_knowledge_insulation=False,
|
| 392 |
+
subtask_loss_weight=0.1,
|
| 393 |
+
freeze_vision_backbone=True,
|
| 394 |
+
),
|
| 395 |
+
data=LeRobotB1KDataConfig(
|
| 396 |
+
repo_id="IliaLarchenko/behavior_224_rgb",
|
| 397 |
+
base_config=DataConfig(
|
| 398 |
+
prompt_from_task=False,
|
| 399 |
+
behavior_dataset_root="~/data/behavior_224_rgb",
|
| 400 |
+
use_per_timestamp_norm=True,
|
| 401 |
+
),
|
| 402 |
+
use_delta_joint_actions=True,
|
| 403 |
+
use_fast_tokenization=False,
|
| 404 |
+
),
|
| 405 |
+
lr_schedule=_optimizer.CosineDecaySchedule(
|
| 406 |
+
warmup_steps=1000,
|
| 407 |
+
peak_lr=1e-4,
|
| 408 |
+
decay_steps=20_000,
|
| 409 |
+
decay_lr=1e-5,
|
| 410 |
+
),
|
| 411 |
+
num_flow_samples=15,
|
| 412 |
+
weight_loader=weight_loaders.PiBehaviorWeightLoader("gs://openpi-assets/checkpoints/pi05_base/params"),
|
| 413 |
+
num_train_steps=200_000,
|
| 414 |
+
assets_base_dir="./outputs/assets",
|
| 415 |
+
checkpoint_base_dir="./outputs/checkpoints",
|
| 416 |
+
num_workers=80,
|
| 417 |
+
save_interval=500,
|
| 418 |
+
keep_period=2000,
|
| 419 |
+
),
|
| 420 |
+
]
|
| 421 |
+
|
| 422 |
+
if len({config.name for config in _CONFIGS}) != len(_CONFIGS):
|
| 423 |
+
raise ValueError("Config names must be unique.")
|
| 424 |
+
_CONFIGS_DICT = {config.name: config for config in _CONFIGS}
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
def cli() -> TrainConfig:
|
| 428 |
+
return tyro.extras.overridable_config_cli({k: (k, v) for k, v in _CONFIGS_DICT.items()})
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
def get_config(config_name: str) -> TrainConfig:
|
| 432 |
+
"""Get a config by name."""
|
| 433 |
+
if config_name not in _CONFIGS_DICT:
|
| 434 |
+
closest = difflib.get_close_matches(config_name, _CONFIGS_DICT.keys(), n=1, cutoff=0.0)
|
| 435 |
+
closest_str = f" Did you mean '{closest[0]}'? " if closest else ""
|
| 436 |
+
raise ValueError(f"Config '{config_name}' not found.{closest_str}")
|
| 437 |
+
|
| 438 |
+
return _CONFIGS_DICT[config_name]
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/da3_extractor.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Inline DA3-GIANT feature extractor (PyTorch, runs in the openpi venv).
|
| 2 |
+
|
| 3 |
+
Produces the SAME feats/ray/depth as the offline cache, but on-the-fly at train time — so
|
| 4 |
+
no precached features are needed (essential for datasets where caching is infeasible, e.g.
|
| 5 |
+
b1k at 30 Hz would be ~3 PB). Frozen, no_grad; the output tensors are handed to the JAX
|
| 6 |
+
model via dlpack.
|
| 7 |
+
|
| 8 |
+
depth_anything_3.api transitively imports rendering/SfM utils (moviepy/gsplat/pycolmap/
|
| 9 |
+
trimesh/evo) that (a) aren't needed for feature extraction and (b) pin numpy<2 (conflicts
|
| 10 |
+
with openpi's numpy 2.x). We stub those modules so nothing gets installed/downgraded.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import concurrent.futures
|
| 14 |
+
import contextlib
|
| 15 |
+
import importlib.util
|
| 16 |
+
import sys
|
| 17 |
+
import types
|
| 18 |
+
|
| 19 |
+
import numpy as np
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
# The frozen extractor is pure GPU work; torch's CPU thread pools only add GIL/scheduler contention
|
| 23 |
+
# with the JAX train loop in the same process (~1000 threads observed). Pin them to 1.
|
| 24 |
+
try:
|
| 25 |
+
torch.set_num_threads(1)
|
| 26 |
+
torch.set_num_interop_threads(1) # only settable before any parallel work; ignore if already set
|
| 27 |
+
except Exception: # noqa: BLE001
|
| 28 |
+
pass
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _to_dev(a, device, dtype=None):
|
| 32 |
+
"""Host array/tensor -> device tensor. A torch tensor (pinned by the DataLoader when
|
| 33 |
+
B1K_TORCH_COLLATE=1) is copied with non_blocking=True so the H2D is an async DMA that overlaps
|
| 34 |
+
compute, instead of a synchronous pageable copy. Numpy input keeps the original behavior."""
|
| 35 |
+
if isinstance(a, torch.Tensor):
|
| 36 |
+
t = a.to(device, non_blocking=True)
|
| 37 |
+
return t.to(dtype) if dtype is not None and t.dtype != dtype else t
|
| 38 |
+
return torch.as_tensor(a, device=device, dtype=dtype)
|
| 39 |
+
|
| 40 |
+
_DA3_SRC = "/work/jack/projects/Depth-Anything-3/src"
|
| 41 |
+
_GEOSTACK = "/work/jack/da3xvla_src/DA3-XVLA-cache/models/da3_for_geostack.py"
|
| 42 |
+
_IMAGENET_MEAN = torch.tensor([0.485, 0.456, 0.406]).view(1, 1, 3, 1, 1)
|
| 43 |
+
_IMAGENET_STD = torch.tensor([0.229, 0.224, 0.225]).view(1, 1, 3, 1, 1)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class _AutoStub(types.ModuleType):
|
| 47 |
+
"""Module stub that returns a dummy callable for any non-dunder attribute + acts as a package."""
|
| 48 |
+
|
| 49 |
+
__path__: list = []
|
| 50 |
+
|
| 51 |
+
def __getattr__(self, name):
|
| 52 |
+
if name.startswith("__"):
|
| 53 |
+
raise AttributeError(name)
|
| 54 |
+
return lambda *a, **k: None
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def _install_stubs():
|
| 58 |
+
for name in ("moviepy", "moviepy.editor", "gsplat", "pycolmap", "trimesh",
|
| 59 |
+
"depth_anything_3.utils.export", "depth_anything_3.utils.pose_align"):
|
| 60 |
+
sys.modules.setdefault(name, _AutoStub(name))
|
| 61 |
+
if hasattr(sys.modules["moviepy"], "__dict__"):
|
| 62 |
+
sys.modules["moviepy"].editor = sys.modules["moviepy.editor"]
|
| 63 |
+
if _DA3_SRC not in sys.path:
|
| 64 |
+
sys.path.insert(0, _DA3_SRC)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def _load_da3_class():
|
| 68 |
+
_install_stubs()
|
| 69 |
+
spec = importlib.util.spec_from_file_location("_da3_for_geostack", _GEOSTACK)
|
| 70 |
+
mod = importlib.util.module_from_spec(spec)
|
| 71 |
+
spec.loader.exec_module(mod)
|
| 72 |
+
return mod.DA3LargeForGeoStack
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def rescale_intrinsics(intr: np.ndarray, src_hw, dst_hw) -> np.ndarray:
|
| 76 |
+
"""Rescale pixel K [.,3,3] from src (H,W) to dst (H,W). fx,cx by W-ratio; fy,cy by H-ratio."""
|
| 77 |
+
sh, sw = src_hw
|
| 78 |
+
dh, dw = dst_hw
|
| 79 |
+
rw, rh = dw / sw, dh / sh
|
| 80 |
+
out = np.array(intr, dtype=np.float32, copy=True)
|
| 81 |
+
out[..., 0, 0] *= rw
|
| 82 |
+
out[..., 0, 2] *= rw
|
| 83 |
+
out[..., 1, 1] *= rh
|
| 84 |
+
out[..., 1, 2] *= rh
|
| 85 |
+
return out
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class DA3InlineExtractor:
|
| 89 |
+
"""Frozen DA3-GIANT posed multi-view extractor, REPLICATED one-per-GPU.
|
| 90 |
+
|
| 91 |
+
Each visible GPU holds its own frozen DA3-GIANT copy and runs the forward on ONLY its slice of the
|
| 92 |
+
batch, all GPUs concurrently (one thread per device; CUDA kernels are async per device, and torch
|
| 93 |
+
releases the GIL during them). This removes the single-GPU serial bottleneck of the old design, so
|
| 94 |
+
inline extraction scales with GPU count to match the JAX data-parallel training step.
|
| 95 |
+
"""
|
| 96 |
+
|
| 97 |
+
def __init__(
|
| 98 |
+
self,
|
| 99 |
+
model_name: str = "depth-anything/DA3NESTED-GIANT-LARGE-1.1",
|
| 100 |
+
out_layers=(19, 26, 33, 39),
|
| 101 |
+
da3_hw=(252, 336),
|
| 102 |
+
devices=None,
|
| 103 |
+
forward_chunk: int = 16,
|
| 104 |
+
):
|
| 105 |
+
DA3 = _load_da3_class()
|
| 106 |
+
if devices is None:
|
| 107 |
+
# Default SINGLE-GPU (cuda:0): the multi-replica path is correct but thread-based, and the
|
| 108 |
+
# Python GIL serializes the DA3 forward's many kernel launches (~18% concurrency efficiency),
|
| 109 |
+
# so replicating across GPUs doesn't speed it up — it only wastes memory. Pass `devices`
|
| 110 |
+
# explicitly (e.g. for a future multiprocess extractor) to override.
|
| 111 |
+
devices = ["cuda:0" if torch.cuda.is_available() else "cpu"]
|
| 112 |
+
self.devices = list(devices)
|
| 113 |
+
self.da3_hw = tuple(da3_hw)
|
| 114 |
+
self.forward_chunk = int(forward_chunk)
|
| 115 |
+
self.replicas = []
|
| 116 |
+
for dev in self.devices:
|
| 117 |
+
m = (
|
| 118 |
+
DA3(model_name=model_name, out_layers=tuple(out_layers),
|
| 119 |
+
da3_input_h=da3_hw[0], da3_input_w=da3_hw[1], patch_size=14, use_bf16=True)
|
| 120 |
+
.to(dev)
|
| 121 |
+
.eval()
|
| 122 |
+
)
|
| 123 |
+
for p in m.parameters():
|
| 124 |
+
p.requires_grad_(False)
|
| 125 |
+
self.replicas.append(m)
|
| 126 |
+
self._pool = concurrent.futures.ThreadPoolExecutor(max_workers=max(1, len(self.devices)))
|
| 127 |
+
|
| 128 |
+
def _preprocess(self, images: np.ndarray, device) -> torch.Tensor:
|
| 129 |
+
"""images: [B,V,H,W,3] uint8 (or float [0,1]) -> [B,V,3,252,336] ImageNet-normalized on `device`."""
|
| 130 |
+
x = _to_dev(images, device)
|
| 131 |
+
if x.dtype == torch.uint8:
|
| 132 |
+
x = x.float() / 255.0
|
| 133 |
+
elif x.max() > 1.5: # already float but in [0,255]
|
| 134 |
+
x = x.float() / 255.0
|
| 135 |
+
x = x.permute(0, 1, 4, 2, 3) # [B,V,3,H,W]
|
| 136 |
+
x = torch.nn.functional.interpolate(
|
| 137 |
+
x.flatten(0, 1), size=self.da3_hw, mode="bicubic", align_corners=False, antialias=True
|
| 138 |
+
).view(*x.shape[:2], 3, *self.da3_hw)
|
| 139 |
+
x = (x - _IMAGENET_MEAN.to(x)) / _IMAGENET_STD.to(x)
|
| 140 |
+
return x
|
| 141 |
+
|
| 142 |
+
def _run_shard(self, di: int, images: np.ndarray, extrinsics: np.ndarray, intrinsics: np.ndarray,
|
| 143 |
+
return_torch: bool = False):
|
| 144 |
+
"""Run replica `di` over its (pre-sliced) shard, in chunks to bound activation memory.
|
| 145 |
+
return_torch=True keeps the result as torch tensors ON cuda:di (no host round-trip) for the
|
| 146 |
+
DLPack handoff; default returns numpy (host) as before."""
|
| 147 |
+
dev = self.devices[di]
|
| 148 |
+
dev_idx = int(dev.split(":")[1]) if ":" in dev else None
|
| 149 |
+
replica = self.replicas[di]
|
| 150 |
+
chunk = self.forward_chunk
|
| 151 |
+
fc, rc, dc = [], [], []
|
| 152 |
+
ctx = torch.cuda.device(dev_idx) if dev_idx is not None else contextlib.nullcontext()
|
| 153 |
+
with ctx, torch.no_grad(): # set current device so implicit-device tensors land on the right GPU
|
| 154 |
+
for i in range(0, images.shape[0], chunk):
|
| 155 |
+
x = self._preprocess(images[i : i + chunk], dev)
|
| 156 |
+
e = _to_dev(extrinsics[i : i + chunk], dev, torch.float32)
|
| 157 |
+
k = _to_dev(intrinsics[i : i + chunk], dev, torch.float32)
|
| 158 |
+
out = replica.forward_multi_view(x, extrinsics=e, intrinsics=k)
|
| 159 |
+
# Ship feats as bf16 BITS (uint16): the model casts to bf16 anyway (see
|
| 160 |
+
# Pi0._compute_banks), so this is numerically identical to shipping f32 while
|
| 161 |
+
# halving the GPU->CPU->GPU transfer and the collate copies.
|
| 162 |
+
feats = torch.stack(list(out["feats"]), dim=1).to(torch.bfloat16) # [b,4,V,C,h,w]
|
| 163 |
+
ray = out["ray"].float() # [b,V,3,h,w]
|
| 164 |
+
depth = out["depth"]
|
| 165 |
+
if depth is None:
|
| 166 |
+
depth = torch.zeros(ray.shape[0], ray.shape[1], 1, ray.shape[3], ray.shape[4], device=ray.device)
|
| 167 |
+
if return_torch:
|
| 168 |
+
fc.append(feats.view(torch.uint16)); rc.append(ray); dc.append(depth.float())
|
| 169 |
+
else:
|
| 170 |
+
fc.append(feats.view(torch.uint16).cpu().numpy())
|
| 171 |
+
rc.append(ray.cpu().numpy())
|
| 172 |
+
dc.append(depth.float().cpu().numpy())
|
| 173 |
+
if return_torch:
|
| 174 |
+
return torch.cat(fc, 0), torch.cat(rc, 0), torch.cat(dc, 0) # torch tensors on cuda:di
|
| 175 |
+
return np.concatenate(fc, 0), np.concatenate(rc, 0), np.concatenate(dc, 0)
|
| 176 |
+
|
| 177 |
+
def extract_shards_torch(self, images: np.ndarray, extrinsics: np.ndarray, intrinsics: np.ndarray):
|
| 178 |
+
"""Like extract() but returns per-shard torch GPU tensors (feats,ray,depth) each on cuda:di,
|
| 179 |
+
WITHOUT the host round-trip. The caller hands them to JAX via DLPack (GPU->GPU over NVLink).
|
| 180 |
+
Returns a list of len(devices) tuples, shard k on self.devices[k]."""
|
| 181 |
+
b = int(images.shape[0])
|
| 182 |
+
nd = len(self.devices)
|
| 183 |
+
bounds = [round(i * b / nd) for i in range(nd + 1)]
|
| 184 |
+
futs = {}
|
| 185 |
+
for di in range(nd):
|
| 186 |
+
s, e = bounds[di], bounds[di + 1]
|
| 187 |
+
if s >= e:
|
| 188 |
+
continue
|
| 189 |
+
futs[di] = self._pool.submit(self._run_shard, di, images[s:e], extrinsics[s:e], intrinsics[s:e], True)
|
| 190 |
+
return [futs[di].result() for di in sorted(futs)]
|
| 191 |
+
|
| 192 |
+
def extract(self, images: np.ndarray, extrinsics: np.ndarray, intrinsics: np.ndarray):
|
| 193 |
+
"""images [B,V,H,W,3]; extrinsics [B,V,4,4] w2c; intrinsics [B,V,3,3] AT 252x336.
|
| 194 |
+
|
| 195 |
+
Splits the batch across all replicas/GPUs and runs the forwards concurrently.
|
| 196 |
+
Returns numpy: feats [B,4,V,1536,18,24] f32, ray [B,V,3,18,24] f32, depth [B,V,1,18,24] f32.
|
| 197 |
+
"""
|
| 198 |
+
b = int(images.shape[0])
|
| 199 |
+
nd = len(self.devices)
|
| 200 |
+
bounds = [round(i * b / nd) for i in range(nd + 1)]
|
| 201 |
+
futs = {}
|
| 202 |
+
for di in range(nd):
|
| 203 |
+
s, e = bounds[di], bounds[di + 1]
|
| 204 |
+
if s >= e:
|
| 205 |
+
continue
|
| 206 |
+
futs[di] = self._pool.submit(self._run_shard, di, images[s:e], extrinsics[s:e], intrinsics[s:e])
|
| 207 |
+
parts = [futs[di].result() for di in sorted(futs)]
|
| 208 |
+
return (
|
| 209 |
+
np.concatenate([p[0] for p in parts], axis=0),
|
| 210 |
+
np.concatenate([p[1] for p in parts], axis=0),
|
| 211 |
+
np.concatenate([p[2] for p in parts], axis=0),
|
| 212 |
+
)
|
legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/weight_loaders.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Weight loaders for PI_BEHAVIOR model initialization from Pi05 checkpoints.
|
| 2 |
+
|
| 3 |
+
Reference: https://github.com/Physical-Intelligence
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import dataclasses
|
| 7 |
+
import logging
|
| 8 |
+
import re
|
| 9 |
+
|
| 10 |
+
import flax.traverse_util
|
| 11 |
+
import numpy as np
|
| 12 |
+
|
| 13 |
+
import openpi.models.model as _model
|
| 14 |
+
import openpi.shared.array_typing as at
|
| 15 |
+
import openpi.shared.download as download
|
| 16 |
+
|
| 17 |
+
# Re-export base loaders from OpenPI
|
| 18 |
+
from openpi.training.weight_loaders import (
|
| 19 |
+
WeightLoader,
|
| 20 |
+
NoOpWeightLoader,
|
| 21 |
+
CheckpointWeightLoader,
|
| 22 |
+
_merge_params,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
logger = logging.getLogger(__name__)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@dataclasses.dataclass(frozen=True)
|
| 29 |
+
class PiBehaviorWeightLoader(WeightLoader):
|
| 30 |
+
"""Loads checkpoints for PI_BEHAVIOR model.
|
| 31 |
+
|
| 32 |
+
Automatically detects:
|
| 33 |
+
- Pi05 checkpoint: Loads weights, preserves new PI_BEHAVIOR parameters
|
| 34 |
+
- PI_BEHAVIOR checkpoint: Loads all weights directly
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
params_path: str
|
| 38 |
+
|
| 39 |
+
def load(self, params: at.Params) -> at.Params:
|
| 40 |
+
# Load checkpoint as host arrays so it can be initialized onto the
|
| 41 |
+
# current device topology, even if the released checkpoint used more GPUs.
|
| 42 |
+
params_path = download.maybe_download(self.params_path)
|
| 43 |
+
restored = _model.restore_params(params_path, restore_type=np.ndarray)
|
| 44 |
+
|
| 45 |
+
# Handle nested 'params' key (from some checkpoint formats)
|
| 46 |
+
if isinstance(restored, dict) and "params" in restored:
|
| 47 |
+
loaded_params = restored["params"]
|
| 48 |
+
else:
|
| 49 |
+
loaded_params = restored
|
| 50 |
+
|
| 51 |
+
# Remove 'value' suffixes (from nnx.State format)
|
| 52 |
+
flat_params = flax.traverse_util.flatten_dict(loaded_params)
|
| 53 |
+
if all(kp[-1] == "value" for kp in flat_params if len(kp) > 0):
|
| 54 |
+
flat_params = {kp[:-1]: v for kp, v in flat_params.items()}
|
| 55 |
+
loaded_params = flax.traverse_util.unflatten_dict(flat_params)
|
| 56 |
+
|
| 57 |
+
# Detect checkpoint type
|
| 58 |
+
has_task_embeddings = 'task_embeddings' in loaded_params
|
| 59 |
+
|
| 60 |
+
if has_task_embeddings:
|
| 61 |
+
# Loading PI_BEHAVIOR checkpoint. New optional adapters are allowed
|
| 62 |
+
# to remain at their initialized values when absent from older checkpoints.
|
| 63 |
+
logging.info("Loading PI_BEHAVIOR checkpoint")
|
| 64 |
+
missing_regex = ".*spatial_action_xattn.*|.*spatial_bank_builder.*|.*spatial_inject.*"
|
| 65 |
+
return _merge_params(loaded_params, params, missing_regex=missing_regex)
|
| 66 |
+
else:
|
| 67 |
+
# Loading Pi05 checkpoint - preserve new PI_BEHAVIOR-specific parameters
|
| 68 |
+
logging.info("Loading Pi05 checkpoint (new PI_BEHAVIOR parameters will use random init)")
|
| 69 |
+
|
| 70 |
+
# These parameters are NEW in PI_BEHAVIOR (not in Pi05), so keep them from params (random init)
|
| 71 |
+
missing_regex = (
|
| 72 |
+
".*task_embeddings.*|"
|
| 73 |
+
".*stage_pred_from_vlm.*|"
|
| 74 |
+
".*task_stage_embeddings.*|"
|
| 75 |
+
".*gate_sincos.*|"
|
| 76 |
+
".*gate_task_stage.*|"
|
| 77 |
+
".*gate_task.*|"
|
| 78 |
+
".*fusion_layer.*|"
|
| 79 |
+
".*stage_projection.*|"
|
| 80 |
+
".*task_subtask_fusion.*|"
|
| 81 |
+
".*fast_token_embedding.*|"
|
| 82 |
+
".*fast_token_proj.*|"
|
| 83 |
+
".*kv_transform.*|"
|
| 84 |
+
".*spatial_action_xattn.*|"
|
| 85 |
+
".*spatial_bank_builder.*|"
|
| 86 |
+
".*spatial_inject.*"
|
| 87 |
+
)
|
| 88 |
+
return _merge_params(loaded_params, params, missing_regex=missing_regex)
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/_METADATA
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/_sharding
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/array_metadatas/process_0
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"array_metadatas": [{"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoder_norm.bias.value", "write_shape": [384], "chunk_shape": [384], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoder_norm.scale.value", "write_shape": [384], "chunk_shape": [384], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.LayerNorm_0.bias.value", "write_shape": [9, 1152], "chunk_shape": [9, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.LayerNorm_0.scale.value", "write_shape": [9, 1152], "chunk_shape": [9, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.LayerNorm_1.bias.value", "write_shape": [9, 1152], "chunk_shape": [9, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.LayerNorm_1.scale.value", "write_shape": [9, 1152], "chunk_shape": [9, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MlpBlock_0.Dense_0.bias.value", "write_shape": [9, 4304], "chunk_shape": [9, 4304], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MlpBlock_0.Dense_0.kernel.value", "write_shape": [9, 1152, 4304], "chunk_shape": [9, 1152, 4304], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MlpBlock_0.Dense_1.bias.value", "write_shape": [9, 1152], "chunk_shape": [9, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MlpBlock_0.Dense_1.kernel.value", "write_shape": [9, 4304, 1152], "chunk_shape": [9, 4304, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.key.bias.value", "write_shape": [9, 16, 72], "chunk_shape": [9, 16, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.key.kernel.value", "write_shape": [9, 1152, 16, 72], "chunk_shape": [9, 1152, 16, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.out.bias.value", "write_shape": [9, 1152], "chunk_shape": [9, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.out.kernel.value", "write_shape": [9, 16, 72, 1152], "chunk_shape": [9, 16, 72, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.query.bias.value", "write_shape": [9, 16, 72], "chunk_shape": [9, 16, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.query.kernel.value", "write_shape": [9, 1152, 16, 72], "chunk_shape": [9, 1152, 16, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.value.bias.value", "write_shape": [9, 16, 72], "chunk_shape": [9, 16, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.value.kernel.value", "write_shape": [9, 1152, 16, 72], "chunk_shape": [9, 1152, 16, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.embedding.bias.value", "write_shape": [384], "chunk_shape": [384], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.embedding.kernel.value", "write_shape": [14, 14, 1, 1152], "chunk_shape": [14, 14, 1, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.head.bias.value", "write_shape": [2048], "chunk_shape": [2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.head.kernel.value", "write_shape": [384, 2048], "chunk_shape": [384, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.pos_embedding.value", "write_shape": [1, 256, 384], "chunk_shape": [1, 256, 384], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.embedder.input_embedding.value", "write_shape": [257152, 2048], "chunk_shape": [257152, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.final_norm.scale.value", "write_shape": [2048], "chunk_shape": [2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.final_norm_1.Dense_0.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.final_norm_1.Dense_0.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.attn_vec_einsum.w.value", "write_shape": [6, 8, 256, 2048], "chunk_shape": [6, 8, 256, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.attn_vec_einsum_1.w.value", "write_shape": [6, 8, 256, 1024], "chunk_shape": [6, 8, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.kv_einsum.w.value", "write_shape": [6, 2, 1, 2048, 256], "chunk_shape": [6, 2, 1, 2048, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.kv_einsum_1.w.value", "write_shape": [6, 2, 1, 1024, 256], "chunk_shape": [6, 2, 1, 1024, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.q_einsum.w.value", "write_shape": [6, 8, 2048, 256], "chunk_shape": [6, 8, 2048, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.q_einsum_1.w.value", "write_shape": [6, 8, 1024, 256], "chunk_shape": [6, 8, 1024, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.mlp.gating_einsum.value", "write_shape": [6, 2, 2048, 16384], "chunk_shape": [6, 2, 2048, 16384], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.mlp.linear.value", "write_shape": [6, 16384, 2048], "chunk_shape": [6, 16384, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.mlp_1.gating_einsum.value", "write_shape": [6, 2, 1024, 4096], "chunk_shape": [6, 2, 1024, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.mlp_1.linear.value", "write_shape": [6, 4096, 1024], "chunk_shape": [6, 4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_attention_norm.scale.value", "write_shape": [6, 2048], "chunk_shape": [6, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_attention_norm_1.Dense_0.bias.value", "write_shape": [6, 3072], "chunk_shape": [6, 3072], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_attention_norm_1.Dense_0.kernel.value", "write_shape": [6, 1024, 3072], "chunk_shape": [6, 1024, 3072], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_ffw_norm.scale.value", "write_shape": [6, 2048], "chunk_shape": [6, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_ffw_norm_1.Dense_0.bias.value", "write_shape": [6, 3072], "chunk_shape": [6, 3072], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_ffw_norm_1.Dense_0.kernel.value", "write_shape": [6, 1024, 3072], "chunk_shape": [6, 1024, 3072], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_branch.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_branch.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.k_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.k_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.k_rmsnorm.scale.value", "write_shape": [6, 128], "chunk_shape": [6, 128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.kv_norm.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.kv_norm.scale.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.log_gain.value", "write_shape": [6, 8], "chunk_shape": [6, 8], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.out_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.out_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.q_norm.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.q_norm.scale.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.q_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.q_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.q_rmsnorm.scale.value", "write_shape": [6, 128], "chunk_shape": [6, 128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.v_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.v_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.k_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.k_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.k_rmsnorm.scale.value", "write_shape": [6, 128], "chunk_shape": [6, 128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.kv_norm.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.kv_norm.scale.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.log_gain.value", "write_shape": [6, 8], "chunk_shape": [6, 8], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.out_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.out_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.q_norm.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.q_norm.scale.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.q_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.q_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.q_rmsnorm.scale.value", "write_shape": [6, 128], "chunk_shape": [6, 128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.v_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.v_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.merge_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.merge_proj.kernel.value", "write_shape": [6, 2048, 1024], "chunk_shape": [6, 2048, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_branch.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_branch.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.k_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.k_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.k_rmsnorm.scale.value", "write_shape": [6, 128], "chunk_shape": [6, 128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.kv_norm.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.kv_norm.scale.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.log_gain.value", "write_shape": [6, 8], "chunk_shape": [6, 8], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.out_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.out_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.q_norm.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.q_norm.scale.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.q_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.q_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.q_rmsnorm.scale.value", "write_shape": [6, 128], "chunk_shape": [6, 128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.v_proj.bias.value", "write_shape": [6, 1024], "chunk_shape": [6, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.v_proj.kernel.value", "write_shape": [6, 1024, 1024], "chunk_shape": [6, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.action_correlation_cholesky.value", "write_shape": [320, 960], "chunk_shape": [320, 960], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.action_in_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.action_in_proj.kernel.value", "write_shape": [32, 1024], "chunk_shape": [32, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.action_out_proj.bias.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.action_out_proj.kernel.value", "write_shape": [1024, 32], "chunk_shape": [1024, 32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fast_token_embedding.embedding.value", "write_shape": [1024, 2048], "chunk_shape": [1024, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fast_token_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fast_token_proj.kernel.value", "write_shape": [2048, 1024], "chunk_shape": [2048, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fusion_layer1.bias.value", "write_shape": [4096], "chunk_shape": [4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fusion_layer1.kernel.value", "write_shape": [4096, 4096], "chunk_shape": [4096, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fusion_layer2.bias.value", "write_shape": [2048], "chunk_shape": [2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fusion_layer2.kernel.value", "write_shape": [4096, 2048], "chunk_shape": [4096, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_sincos.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_sincos.kernel.value", "write_shape": [4096, 1024], "chunk_shape": [4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_task.bias.value", "write_shape": [2048], "chunk_shape": [2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_task.kernel.value", "write_shape": [4096, 2048], "chunk_shape": [4096, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_task_stage.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_task_stage.kernel.value", "write_shape": [4096, 1024], "chunk_shape": [4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.kv_transform.k_bias.value", "write_shape": [6, 1, 256], "chunk_shape": [6, 1, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.kv_transform.k_coeffs.value", "write_shape": [6, 18], "chunk_shape": [6, 18], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.kv_transform.v_bias.value", "write_shape": [6, 1, 256], "chunk_shape": [6, 1, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.kv_transform.v_coeffs.value", "write_shape": [6, 18], "chunk_shape": [6, 18], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.bank_token_embeds.left.value", "write_shape": [1, 32, 1024], "chunk_shape": [1, 32, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.bank_token_embeds.main.value", "write_shape": [1, 128, 1024], "chunk_shape": [1, 128, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.bank_token_embeds.right.value", "write_shape": [1, 32, 1024], "chunk_shape": [1, 32, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_pose_mlp.fc1.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_pose_mlp.fc1.kernel.value", "write_shape": [4, 256], "chunk_shape": [4, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_pose_mlp.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_pose_mlp.fc2.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.k_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.k_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.k_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.out_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.out_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.q_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.q_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.q_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.v_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.v_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.kv_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.kv_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.q_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.q_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.1.fc1.bias.value", "write_shape": [4096], "chunk_shape": [4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.1.fc1.kernel.value", "write_shape": [1024, 4096], "chunk_shape": [1024, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.1.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.1.fc2.kernel.value", "write_shape": [4096, 1024], "chunk_shape": [4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.1.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.1.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.k_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.k_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.k_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.out_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.out_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.q_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.q_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.q_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.v_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.v_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.kv_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.kv_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.q_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.q_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.1.fc1.bias.value", "write_shape": [4096], "chunk_shape": [4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.1.fc1.kernel.value", "write_shape": [1024, 4096], "chunk_shape": [1024, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.1.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.1.fc2.kernel.value", "write_shape": [4096, 1024], "chunk_shape": [4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.1.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.1.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.depth_mlp.fc1.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.depth_mlp.fc1.kernel.value", "write_shape": [1, 256], "chunk_shape": [1, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.depth_mlp.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.depth_mlp.fc2.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.k_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.k_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.k_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.out_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.out_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.q_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.q_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.q_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.v_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.v_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.kv_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.kv_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.q_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.q_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.1.fc1.bias.value", "write_shape": [4096], "chunk_shape": [4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.1.fc1.kernel.value", "write_shape": [1024, 4096], "chunk_shape": [1024, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.1.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.1.fc2.kernel.value", "write_shape": [4096, 1024], "chunk_shape": [4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.1.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.1.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.k_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.k_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.k_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.out_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.out_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.q_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.q_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.q_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.v_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.v_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.kv_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.kv_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.q_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.q_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.1.fc1.bias.value", "write_shape": [4096], "chunk_shape": [4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.1.fc1.kernel.value", "write_shape": [1024, 4096], "chunk_shape": [1024, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.1.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.1.fc2.kernel.value", "write_shape": [4096, 1024], "chunk_shape": [4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.1.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.1.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.k_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.k_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.k_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.out_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.out_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.q_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.q_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.q_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.v_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.v_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.kv_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.kv_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.q_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.q_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.1.fc1.bias.value", "write_shape": [4096], "chunk_shape": [4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.1.fc1.kernel.value", "write_shape": [1024, 4096], "chunk_shape": [1024, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.1.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.1.fc2.kernel.value", "write_shape": [4096, 1024], "chunk_shape": [4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.1.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.1.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.k_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.k_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.k_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.out_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.out_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.q_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.q_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.q_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.v_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.v_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.kv_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.kv_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.q_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.q_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.1.fc1.bias.value", "write_shape": [4096], "chunk_shape": [4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.1.fc1.kernel.value", "write_shape": [1024, 4096], "chunk_shape": [1024, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.1.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.1.fc2.kernel.value", "write_shape": [4096, 1024], "chunk_shape": [4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.1.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.1.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.k_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.k_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.k_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.out_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.out_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.q_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.q_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.q_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.v_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.v_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.kv_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.kv_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.q_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.q_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.1.fc1.bias.value", "write_shape": [4096], "chunk_shape": [4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.1.fc1.kernel.value", "write_shape": [1024, 4096], "chunk_shape": [1024, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.1.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.1.fc2.kernel.value", "write_shape": [4096, 1024], "chunk_shape": [4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.1.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.1.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.k_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.k_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.k_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.out_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.out_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.q_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.q_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.q_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.v_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.v_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.kv_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.kv_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.q_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.q_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.1.fc1.bias.value", "write_shape": [4096], "chunk_shape": [4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.1.fc1.kernel.value", "write_shape": [1024, 4096], "chunk_shape": [1024, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.1.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.1.fc2.kernel.value", "write_shape": [4096, 1024], "chunk_shape": [4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.1.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.1.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_embed.value", "write_shape": [4, 1024], "chunk_shape": [4, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_fuse.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_fuse.kernel.value", "write_shape": [4096, 1024], "chunk_shape": [4096, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.fc1.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.fc1.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.fc2.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.fc1.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.fc1.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.fc2.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.fc1.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.fc1.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.fc2.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.fc1.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.fc1.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.fc2.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.loc_log_gamma.value", "write_shape": [8], "chunk_shape": [8], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.fc1.bias.value", "write_shape": [2048], "chunk_shape": [2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.fc1.kernel.value", "write_shape": [1024, 2048], "chunk_shape": [1024, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.fc2.kernel.value", "write_shape": [2048, 1024], "chunk_shape": [2048, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.out_ln.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.out_ln.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.query.value", "write_shape": [1, 32, 1024], "chunk_shape": [1, 32, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.k_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.k_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.k_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.log_gain.value", "write_shape": [8], "chunk_shape": [8], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.out_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.out_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.q_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.q_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.q_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.v_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.v_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.kv_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.kv_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.q_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.q_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.loc_log_gamma.value", "write_shape": [8], "chunk_shape": [8], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.fc1.bias.value", "write_shape": [2048], "chunk_shape": [2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.fc1.kernel.value", "write_shape": [1024, 2048], "chunk_shape": [1024, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.fc2.kernel.value", "write_shape": [2048, 1024], "chunk_shape": [2048, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.out_ln.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.out_ln.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.query.value", "write_shape": [1, 128, 1024], "chunk_shape": [1, 128, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.k_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.k_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.k_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.log_gain.value", "write_shape": [8], "chunk_shape": [8], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.out_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.out_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.q_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.q_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.q_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.v_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.v_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.kv_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.kv_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.q_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.q_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.loc_log_gamma.value", "write_shape": [8], "chunk_shape": [8], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.fc1.bias.value", "write_shape": [2048], "chunk_shape": [2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.fc1.kernel.value", "write_shape": [1024, 2048], "chunk_shape": [1024, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.fc2.kernel.value", "write_shape": [2048, 1024], "chunk_shape": [2048, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.out_ln.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.out_ln.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.query.value", "write_shape": [1, 32, 1024], "chunk_shape": [1, 32, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.k_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.k_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.k_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.log_gain.value", "write_shape": [8], "chunk_shape": [8], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.out_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.out_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.q_ln.scale.value", "write_shape": [128], "chunk_shape": [128], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.q_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.q_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.v_proj.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.v_proj.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.kv_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.kv_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.q_norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.q_norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pos2d_mlp.fc1.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pos2d_mlp.fc1.kernel.value", "write_shape": [2, 256], "chunk_shape": [2, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pos2d_mlp.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pos2d_mlp.fc2.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.ray_mlp.fc1.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.ray_mlp.fc1.kernel.value", "write_shape": [2, 256], "chunk_shape": [2, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.ray_mlp.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.ray_mlp.fc2.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.fc1.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.fc1.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.fc2.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.fc2.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.norm.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.norm.scale.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.view_embed.embedding.value", "write_shape": [1, 1024], "chunk_shape": [1, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.stage_pred_from_vlm.bias.value", "write_shape": [5], "chunk_shape": [5], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.stage_pred_from_vlm.kernel.value", "write_shape": [2048, 5], "chunk_shape": [2048, 5], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.stage_projection.bias.value", "write_shape": [2048], "chunk_shape": [2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.stage_projection.kernel.value", "write_shape": [2048, 2048], "chunk_shape": [2048, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.task_embeddings.embedding.value", "write_shape": [50, 2048], "chunk_shape": [50, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.task_stage_embeddings.embedding.value", "write_shape": [596, 1024], "chunk_shape": [596, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.time_mlp_in.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.time_mlp_in.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.time_mlp_out.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.time_mlp_out.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}]}
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/d/39ef2869a2331f89bed4f6c27fedb8f5
ADDED
|
Binary file (27.6 kB). View file
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/manifest.ocdbt
ADDED
|
Binary file (120 Bytes). View file
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/112304e1e0ddb67e7bd397f812397e84
ADDED
|
Binary file (2.42 kB). View file
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/2608e0b98840e07c0bddb547c83352b5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d4c123c48e32257726a2c1cfcaeff8bd6ca530d0712ad4da4c0fbb6f1229c86c
|
| 3 |
+
size 1956982784
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/26312df420be0ded6f7d2968b9eb31e4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b1b9a533fe3888a643eaf89382a748c6b85bbf1ba249bdf3aa583ca7bb72a276
|
| 3 |
+
size 1492627456
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/3080ccd8a463b13014c46f0a14c6ef53
ADDED
|
Binary file (2.02 kB). View file
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/30d721906d6b6b5684b2658140e82d48
ADDED
|
Binary file (2.04 kB). View file
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/3111fc988234a1a8478acb3aac9e8640
ADDED
|
Binary file (2.31 kB). View file
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/32e999b158e705cc43e62af68ed2c026
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ae2d4c5fcd211d44c2bc0c30580b699fdb9332688199be866315269067005824
|
| 3 |
+
size 746663936
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/35232fd09a73f92d9d541123f677d7b6
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fd5cdbf1e7a7e5eec169c3dd6cf976c12c0dd834d478f83bc98a96a8948ddf57
|
| 3 |
+
size 2152910848
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/398e224cb03720414d6915f28a59809b
ADDED
|
Binary file (705 Bytes). View file
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/5188737e8a9bf878e65eb81e159c24b9
ADDED
|
Binary file (1.46 kB). View file
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/589f02b3a7ab6adaca31058554766308
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9f347a2abba5f327ba89803acb9fb93f2d4a980b737a171dee17b697c5bd6c23
|
| 3 |
+
size 73719808
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/58cb578d8033aedd34caa72dbd8345ae
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:99c0f5956846493132347edcd735bdc9c3355c4f32f2de9e8ae4d293b3cccfc4
|
| 3 |
+
size 1030218
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/59fbd4933ee42ab436ca86497826afe4
ADDED
|
Binary file (2.74 kB). View file
|
|
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/5d963bbe5fe91155634586f828e91671
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:45d9891bd016efdf1e7fa85ec02118dac070f01901c9ca05c4ba09707a00ded9
|
| 3 |
+
size 746536960
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/60c607b33450be6346df3980b439ad2c
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:75d591bd8c918d49f89311163aa6a81ba69ce3a2e372922debb8d3ddc4495977
|
| 3 |
+
size 1094893568
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/742cb389e1ad457ef05645486d8511fa
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6d40b1034db1cbd91906143fc65c44ba9ecfa59b7f8098bec7808ab41bce8c14
|
| 3 |
+
size 746663936
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/7de207fe9d87adc11cbbd47a23474c55
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5b8fcc77df297d8e4c29c48813df65178fac7eff8b8fd0ab720274fd14bb6f22
|
| 3 |
+
size 2985947136
|
legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/8e22384919e66ee762de8aa29a643f1e
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ab05b3ceb3aaad41b011a0b6c7f58cfb0f993bf820f7d12f63f53c5ca3694919
|
| 3 |
+
size 31072256
|