JackLiu0406 commited on
Commit
e042166
·
verified ·
1 Parent(s): 29a4321

legacy: copy task12_da3_large_gtdepth_newbank_step19999 -> legacy/task12_da3_large_gtdepth_newbank_step19999

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +13 -0
  2. legacy/task12_da3_large_gtdepth_newbank_step19999/README.md +217 -0
  3. legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/metadata.json +25 -0
  4. legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/processing_action_tokenizer.py +158 -0
  5. legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/processor_config.json +11 -0
  6. legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/special_tokens_map.json +1 -0
  7. legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/tokenizer.json +0 -0
  8. legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/tokenizer_config.json +11 -0
  9. legacy/task12_da3_large_gtdepth_newbank_step19999/assets/IliaLarchenko/behavior_224_rgb/norm_stats.json +3 -0
  10. legacy/task12_da3_large_gtdepth_newbank_step19999/code/launch/launch_b1k_newbank.sh +43 -0
  11. legacy/task12_da3_large_gtdepth_newbank_step19999/code/launch/launch_b1k_task12_large_gtdepth.sh +18 -0
  12. legacy/task12_da3_large_gtdepth_newbank_step19999/code/launch/launch_b1k_v2.sh +114 -0
  13. legacy/task12_da3_large_gtdepth_newbank_step19999/code/openpi/src/openpi/training/data_loader.py +676 -0
  14. legacy/task12_da3_large_gtdepth_newbank_step19999/code/scripts/serve_b1k.py +190 -0
  15. legacy/task12_da3_large_gtdepth_newbank_step19999/code/scripts/train_2026.py +180 -0
  16. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/observation.py +176 -0
  17. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/pi_behavior.py +1327 -0
  18. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/pi_behavior_config.py +291 -0
  19. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/models/spatial_da3.py +593 -0
  20. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/policies/b1k_policy.py +156 -0
  21. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/policies/pi_behavior_policy.py +125 -0
  22. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/policies/policy_config.py +119 -0
  23. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/b1k_2026.py +414 -0
  24. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/b1k_da3.py +416 -0
  25. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/config.py +438 -0
  26. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/da3_extractor.py +212 -0
  27. legacy/task12_da3_large_gtdepth_newbank_step19999/code/src/b1k/training/weight_loaders.py +88 -0
  28. legacy/task12_da3_large_gtdepth_newbank_step19999/params/_METADATA +0 -0
  29. legacy/task12_da3_large_gtdepth_newbank_step19999/params/_sharding +0 -0
  30. legacy/task12_da3_large_gtdepth_newbank_step19999/params/array_metadatas/process_0 +1 -0
  31. legacy/task12_da3_large_gtdepth_newbank_step19999/params/d/39ef2869a2331f89bed4f6c27fedb8f5 +0 -0
  32. legacy/task12_da3_large_gtdepth_newbank_step19999/params/manifest.ocdbt +0 -0
  33. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/112304e1e0ddb67e7bd397f812397e84 +0 -0
  34. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/2608e0b98840e07c0bddb547c83352b5 +3 -0
  35. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/26312df420be0ded6f7d2968b9eb31e4 +3 -0
  36. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/3080ccd8a463b13014c46f0a14c6ef53 +0 -0
  37. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/30d721906d6b6b5684b2658140e82d48 +0 -0
  38. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/3111fc988234a1a8478acb3aac9e8640 +0 -0
  39. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/32e999b158e705cc43e62af68ed2c026 +3 -0
  40. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/35232fd09a73f92d9d541123f677d7b6 +3 -0
  41. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/398e224cb03720414d6915f28a59809b +0 -0
  42. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/5188737e8a9bf878e65eb81e159c24b9 +0 -0
  43. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/589f02b3a7ab6adaca31058554766308 +3 -0
  44. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/58cb578d8033aedd34caa72dbd8345ae +3 -0
  45. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/59fbd4933ee42ab436ca86497826afe4 +0 -0
  46. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/5d963bbe5fe91155634586f828e91671 +3 -0
  47. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/60c607b33450be6346df3980b439ad2c +3 -0
  48. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/742cb389e1ad457ef05645486d8511fa +3 -0
  49. legacy/task12_da3_large_gtdepth_newbank_step19999/params/ocdbt.process_0/d/7de207fe9d87adc11cbbd47a23474c55 +3 -0
  50. 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