JackLiu0406 commited on
Commit
24b0fc6
·
verified ·
1 Parent(s): cd37afc

legacy: copy vggt_newbank_boxing_gloves_step19999 -> legacy/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_step19999/README.md +100 -0
  3. legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/metadata.json +25 -0
  4. legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/processing_action_tokenizer.py +158 -0
  5. legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/processor_config.json +11 -0
  6. legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/special_tokens_map.json +1 -0
  7. legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/tokenizer.json +0 -0
  8. legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/tokenizer_config.json +11 -0
  9. legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/norm_stats.json +3 -0
  10. legacy/vggt_newbank_boxing_gloves_step19999/code/reference/eval_b1k_wrapper.py +307 -0
  11. legacy/vggt_newbank_boxing_gloves_step19999/code/reference/policy_config.py +119 -0
  12. legacy/vggt_newbank_boxing_gloves_step19999/code/reference/serve_b1k.py +190 -0
  13. legacy/vggt_newbank_boxing_gloves_step19999/code/scripts/train_2026.py +180 -0
  14. legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/models/observation.py +176 -0
  15. legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/models/pi_behavior.py +1327 -0
  16. legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/models/pi_behavior_config.py +291 -0
  17. legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/models/spatial_da3.py +593 -0
  18. legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/training/b1k_2026.py +414 -0
  19. legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/training/b1k_da3.py +321 -0
  20. legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/training/vggt_extractor.py +208 -0
  21. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/__init__.py +13 -0
  22. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/__init__.py +9 -0
  23. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/aggregator.py +250 -0
  24. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/__init__.py +11 -0
  25. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/camera_head.py +80 -0
  26. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/dense_head.py +308 -0
  27. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/text_alignment_head.py +79 -0
  28. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/utils.py +108 -0
  29. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/__init__.py +27 -0
  30. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/attention.py +184 -0
  31. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/block.py +274 -0
  32. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/ffn_layers.py +83 -0
  33. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/layer_scale.py +35 -0
  34. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/patch_embed.py +95 -0
  35. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/rms_norm.py +30 -0
  36. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/rope_position_encoding.py +127 -0
  37. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/utils.py +136 -0
  38. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/vision_transformer.py +424 -0
  39. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/vggt_omega.py +88 -0
  40. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/__init__.py +7 -0
  41. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/geometry.py +34 -0
  42. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/load_fn.py +129 -0
  43. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/pose_enc.py +52 -0
  44. legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/rotation.py +130 -0
  45. legacy/vggt_newbank_boxing_gloves_step19999/params/_METADATA +0 -0
  46. legacy/vggt_newbank_boxing_gloves_step19999/params/_sharding +0 -0
  47. legacy/vggt_newbank_boxing_gloves_step19999/params/array_metadatas/process_0 +1 -0
  48. legacy/vggt_newbank_boxing_gloves_step19999/params/d/a46900611b6ae8d52cc9c73344a60cd5 +3 -0
  49. legacy/vggt_newbank_boxing_gloves_step19999/params/manifest.ocdbt +0 -0
  50. legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/04eb8ca89e0a583a9990df03b6a7b2a1 +0 -0
.gitattributes CHANGED
@@ -2497,3 +2497,16 @@ legacy/task12_da3_large_gtdepth_v2_pointmap_step29999_ext30k/params/ocdbt.proces
2497
  legacy/task12_da3_large_gtdepth_v2_pointmap_step29999_ext30k/params/ocdbt.process_0/d/d98a3fad37dee54708c107db1c8fb9ec filter=lfs diff=lfs merge=lfs -text
2498
  legacy/task12_da3_large_gtdepth_v2_pointmap_step29999_ext30k/params/ocdbt.process_0/d/e253ce1315c04daa9ed15b1288857241 filter=lfs diff=lfs merge=lfs -text
2499
  legacy/task12_da3_large_gtdepth_v2_pointmap_step29999_ext30k/params/ocdbt.process_0/d/ffd84587e396735aab5be6c968b420b4 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2497
  legacy/task12_da3_large_gtdepth_v2_pointmap_step29999_ext30k/params/ocdbt.process_0/d/d98a3fad37dee54708c107db1c8fb9ec filter=lfs diff=lfs merge=lfs -text
2498
  legacy/task12_da3_large_gtdepth_v2_pointmap_step29999_ext30k/params/ocdbt.process_0/d/e253ce1315c04daa9ed15b1288857241 filter=lfs diff=lfs merge=lfs -text
2499
  legacy/task12_da3_large_gtdepth_v2_pointmap_step29999_ext30k/params/ocdbt.process_0/d/ffd84587e396735aab5be6c968b420b4 filter=lfs diff=lfs merge=lfs -text
2500
+ legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/norm_stats.json filter=lfs diff=lfs merge=lfs -text
2501
+ legacy/vggt_newbank_boxing_gloves_step19999/params/d/a46900611b6ae8d52cc9c73344a60cd5 filter=lfs diff=lfs merge=lfs -text
2502
+ legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/79393094e7346483b9ad835f750e221d filter=lfs diff=lfs merge=lfs -text
2503
+ legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/91a085ae6e1894bb048d726fc0eb5095 filter=lfs diff=lfs merge=lfs -text
2504
+ legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/b4e08dc278ff9559ca81bab2466ad7eb filter=lfs diff=lfs merge=lfs -text
2505
+ legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/b6d0beee53fde61e4a4cad79fe82d2bb filter=lfs diff=lfs merge=lfs -text
2506
+ legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/c89bf5c18befe2a4541c9878cd67c634 filter=lfs diff=lfs merge=lfs -text
2507
+ legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/d35b7454f6d0270967e4a343cf9a0532 filter=lfs diff=lfs merge=lfs -text
2508
+ legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/df61096e2ebc9bed5ca155e01070f51d filter=lfs diff=lfs merge=lfs -text
2509
+ legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/eca7e002c9a0d1b4044e00ed6a6b7c72 filter=lfs diff=lfs merge=lfs -text
2510
+ legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/f054a053cf6d0352891dfa0126e38abb filter=lfs diff=lfs merge=lfs -text
2511
+ legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/f4c80a2f2a49d019fd150fe66b20b6f6 filter=lfs diff=lfs merge=lfs -text
2512
+ legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/fd79ec0eed15dfaa789ab7d2b3cd12ea filter=lfs diff=lfs merge=lfs -text
legacy/vggt_newbank_boxing_gloves_step19999/README.md ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # VGGT-Omega newbank policy — how to run / serve / eval
2
+
3
+ This bundle contains **everything needed to load, serve, and evaluate** the checkpoint
4
+ `JackLiu0406/b1k-checkpoints/vggt_newbank_boxing_gloves_step19999`. Read this + the `code/` files and you
5
+ will know exactly how to run the model.
6
+
7
+ The policy is the 2025 BEHAVIOR-1K winner (PiBehavior = pi0.5 JAX fork: `gemma_2b` VLM + `gemma_300m`
8
+ action expert) with the **DA3-GIANT geometry backbone REPLACED by VGGT-Omega-1B**, feeding an *enriched*
9
+ spatial bank (adds depth-confidence, camera pose-encoding, and camera/register global tokens). Base init =
10
+ the 50-task meta checkpoint (`behavior-1k/2025-challenge-demos`). Task = `clean_boxing_gloves`, **step
11
+ 19999/20000 (FINAL checkpoint, action_loss ≈ 0.041)**.
12
+
13
+ ---
14
+
15
+ ## 0. TL;DR to run
16
+
17
+ 1. Get the code into a `behavior-1k-solution` checkout (file map in §3) and put `third_party/vggt_omega`
18
+ on `sys.path`.
19
+ 2. Get the VGGT weights: `JackLiu0406/vggt-omega-1b` → `vggt_omega_1b_512.pt` (cached under `$HF_HOME`).
20
+ 3. Export the flags in §2 so `build_config()` attaches the VGGT-enriched `da3` config to the model.
21
+ 4. Load `params/` from this checkpoint with **`remove_extra_params=False`** (see §5 — critical).
22
+ 5. At every timestep, run the **VGGT extractor** on the 3 RGB views (§4) to produce the 6 spatial fields,
23
+ put them on the `Observation`, and call `sample_actions`. Camera extrinsics/intrinsics come from the
24
+ sim (GT); rays are computed analytically from the intrinsics.
25
+
26
+ ---
27
+
28
+ ## 1. Checkpoint contents
29
+ - `params/` — inference weights (orbax, bf16). This is what you serve.
30
+ - `assets/` — norm stats (`IliaLarchenko/behavior_224_rgb/norm_stats.json`), tokenizer.
31
+ - (No `train_state/` — that's only for resuming training.)
32
+
33
+ ## 2. Model-build flags (MUST match training or the checkpoint won't load / will mis-serve)
34
+ `scripts/train_2026.py::build_config()` reads these env vars and does
35
+ `model = dataclasses.replace(model, da3=B1KDA3Config(...))`. Base config has `da3=None`; **without these
36
+ the VGGT spatial branch is absent and `params/` won't match.**
37
+ ```
38
+ USE_DA3_FULL=1 USE_VGGT=1 VGGT_PROCESS_RES=256
39
+ DA3_CHANNELS=2048 DA3_GRID_H=16 DA3_GRID_W=16
40
+ DA3_USE_DEPTH_CONF=1 DA3_USE_POSE_ENC=1 DA3_USE_CAM_TOKENS=1 DA3_CAM_TOKEN_DIM=2048
41
+ DA3_FEAT_INPUT_NORM=1
42
+ DA3_KV_SPLIT=1 DA3_BANK_CENTER=1 DA3_PERC_LOCALITY=1 DA3_CROSS_VIEW=1 DA3_CROSS_VIEW_DEPTH=2
43
+ DA3_DEPTH_DROPOUT=0.5 DA3_BTE_QUERY=1
44
+ DA3_QK_NORM=1 DA3_PERC_NORM_FINAL=1 DA3_PERC_NORM_OUT=0 DA3_POS_EMB_SCALE=0.25
45
+ DA3_LOGIT_GAIN_INIT=3.0 DA3_INJ_GAIN_MAX=8.0 DA3_PERC_LOGIT_GAIN=1 DA3_PERC_GAIN_INIT=3.0 DA3_PERC_GAIN_MAX=8.0
46
+ DA3_SCALE=1.0 DA3_INIT_STD=0.01 DA3_LR_GROUPS=1
47
+ ```
48
+ (`depth_dropout` is inference-inert — it only fires when a dropout rng is passed, which serving does not.)
49
+
50
+ ## 3. File map — where each `code/` file goes in `behavior-1k-solution`
51
+ | bundle file | repo path | what it is |
52
+ |---|---|---|
53
+ | `src/b1k/training/vggt_extractor.py` | same | **NEW** — VGGTInlineExtractor (the backbone). Loads VGGT-Omega, outputs the 6 fields |
54
+ | `src/b1k/models/spatial_da3.py` | same | the enriched `SpatialBankBuilder` (depth_conf / pose_enc / cam_tokens / feat_input_norm) |
55
+ | `src/b1k/models/pi_behavior_config.py` | same | `B1KDA3Config` — all the fields the flags in §2 set |
56
+ | `src/b1k/models/observation.py` | same | `Observation` — the spatial fields incl. `da3_depth_conf/pose_enc/cam_tokens` |
57
+ | `src/b1k/models/pi_behavior.py` | same | `_compute_banks()` (passes the 6 fields to the builder) + the layer-12–17 injection |
58
+ | `src/b1k/training/b1k_da3.py` | same | loader + `batch_transform` (extractor selection via `USE_VGGT`, 6-field plumbing) |
59
+ | `src/b1k/training/b1k_2026.py` | same | `BehaviorV3Dataset` + `da3_fields()` (RGB decode, GT extrinsics, intrinsics from FOCAL_RATIO) |
60
+ | `scripts/train_2026.py` | same | `build_config()` — env → config |
61
+ | `third_party/vggt_omega/` | `<repo>/third_party/vggt_omega` | the VGGT-Omega package (put its parent on sys.path) |
62
+ | `reference/serve_b1k.py`, `reference/eval_b1k_wrapper.py`, `reference/policy_config.py` | — | the serve entrypoints for reference; see §5 |
63
+
64
+ ## 4. The VGGT extractor (`vggt_extractor.py`) — what it produces
65
+ `VGGTInlineExtractor(process_res=256).extract(images[B,V,H,W,3], extrinsics[B,V,4,4], intrinsics[B,V,3,3])`
66
+ returns a 6-tuple (field order matters — `b1k_da3.py` maps them to observation keys):
67
+ 1. `da3_features` `[B,4,V,2048,16,16]` bf16-as-uint16 — 4 VGGT aggregator taps (blocks 4/11/17/23), patch tokens
68
+ 2. `da3_ray` `[B,V,3,16,16]` — **analytic** camera-frame unit ray dirs from the intrinsics (VGGT has no ray head)
69
+ 3. `da3_depth` `[B,V,1,16,16]` — VGGT dense_head depth, pooled to grid (VGGT-PREDICTED, not GT)
70
+ 4. `da3_depth_conf` `[B,V,1,16,16]` — VGGT depth confidence
71
+ 5. `da3_pose_enc` `[B,V,9]` — VGGT camera_head pose enc (trans3+quat4+fov2)
72
+ 6. `da3_cam_tokens` `[B,V,17,2048]` — camera(1)+register(16) global tokens
73
+ Views V=3 in order **(main=zed/head, left wrist, right wrist)**. Feeds `[0,1]` RGB; the aggregator does its
74
+ own ImageNet/ResNet renorm. patch_size 16, so `process_res` must be a multiple of 16.
75
+ NOTE: a warmup pass runs per replica in `__init__` (single-threaded) — needed because `torch.linalg` lazy
76
+ wrappers race across the per-device extraction threads; and rays are analytic (no `torch.linalg.inv`).
77
+
78
+ ## 5. Serving / eval integration (CRITICAL correctness note)
79
+ - Build the policy with `da3 = B1KDA3Config(...)` per §2, and load with
80
+ `train_config.model.load(restore_params(dir/'params'), remove_extra_params=False)`. **`remove_extra_params`
81
+ must be False** so a param mismatch RAISES instead of silently dropping the whole `da3.*` subtree — a
82
+ stock serve (which builds `da3=None`) would otherwise load a lobotomized base model with no spatial branch
83
+ and give meaningless rollouts. (`reference/policy_config.py` is where the current DA3 serve loads params.)
84
+ - Per timestep: decode the 3 RGB views at 256, run the VGGT extractor (§4), and set the six
85
+ `observation.da3_*` fields + `observation.camera_extrinsics` (GT from sim). `da3_features` ship as uint16
86
+ bf16-bits; `_compute_banks` (pi_behavior.py) bitcasts them. Then `model.sample_actions(...)`.
87
+ - The bank the action expert attends to is **145/113/113 tokens** for main/left/right (K + 17 cam tokens).
88
+
89
+ ## 6. Data flow (how the enriched bank is built — see `spatial_da3.py::SpatialBankBuilder.__call__`)
90
+ - **payload (attention values)** = fused VGGT feats (4-tap, LayerNorm'd via `feat_input_norm`) + depth_emb
91
+ (log VGGT depth) + conf_emb (log VGGT depth_conf)
92
+ - **address (attention keys)** = pos_emb(×0.25) + view_emb + ray_emb (world Plücker from analytic ray + GT extrinsics)
93
+ - K/V split → perceiver (locality-biased, K queries) → cross-view fusion (adds cam-pose feat + VGGT pose_enc)
94
+ → language fusion (ModernBERT task tokens) → bank-center → append projected VGGT cam/register tokens
95
+ - injected into action-expert layers 12–17 via cross-attention.
96
+
97
+ ## 7. Sanity checks before trusting an eval
98
+ - A forward should give a finite `action_loss` (~0.041 for this final ckpt on `clean_boxing_gloves`).
99
+ - **Specificity**: zeroing the bank should HURT loss (measured +41%±32 at step 2000 → the spatial branch is
100
+ load-bearing). If zeroing the bank does nothing, the spatial branch was dropped — recheck §5.
legacy/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/special_tokens_map.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {}
legacy/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_step19999/code/reference/eval_b1k_wrapper.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """B1K policy wrapper with action compression, rolling inpainting, and stage voting."""
2
+
3
+ import logging
4
+ import numpy as np
5
+ import torch
6
+ import dataclasses
7
+ from collections import deque
8
+
9
+ from openpi_client.base_policy import BasePolicy
10
+ from openpi_client.image_tools import resize_with_pad
11
+ from b1k.policies.b1k_policy import extract_state_from_proprio
12
+ from b1k.models.pi_behavior_config import TASK_NUM_STAGES
13
+ from b1k.shared.correction_rules import apply_correction_rules, check_gripper_variation
14
+ from omnigibson.learning.utils.eval_utils import PROPRIOCEPTION_INDICES
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ RESIZE_SIZE = 224
19
+
20
+
21
+ @dataclasses.dataclass
22
+ class B1KWrapperConfig:
23
+ """Configuration for B1K policy wrapper execution parameters."""
24
+ actions_to_execute: int = 26
25
+ actions_to_keep: int = 4
26
+ execute_in_n_steps: int = 20
27
+ history_len: int = 3
28
+ votes_to_promote: int = 2
29
+ time_threshold_inpaint: float = 0.3
30
+ num_steps: int = 20
31
+ apply_eval_tricks: bool = True
32
+
33
+
34
+ class B1KPolicyWrapper():
35
+ """B1K policy wrapper for PI_BEHAVIOR models with action compression, rolling inpainting, and stage voting."""
36
+
37
+ def __init__(
38
+ self,
39
+ policy: BasePolicy,
40
+ text_prompt: str = "PI_BEHAVIOR model (task-conditioned)", # Not used, kept for compatibility
41
+ action_horizon: int = 30,
42
+ task_id: int | None = None,
43
+ config: B1KWrapperConfig = None,
44
+ checkpoint_switcher = None,
45
+ ) -> None:
46
+ self.base_policy = policy
47
+ self.policy = policy
48
+ self.checkpoint_switcher = checkpoint_switcher
49
+ self.text_prompt = text_prompt
50
+ self.action_horizon = action_horizon
51
+ self.config = config if config is not None else B1KWrapperConfig()
52
+
53
+ # Validate configuration
54
+ if self.config.actions_to_execute + self.config.actions_to_keep > self.action_horizon:
55
+ raise ValueError(
56
+ f"actions_to_execute + actions_to_keep exceeds action_horizon"
57
+ )
58
+
59
+ # PI_BEHAVIOR specific (always True for B1K)
60
+ self.task_id = task_id
61
+ self.current_stage = 0
62
+ self.prediction_history = deque([], maxlen=self.config.history_len)
63
+
64
+ # Control loop variables
65
+ self.last_actions = None
66
+ self.action_index = 0
67
+ self.step_count = 0
68
+ self.prediction_count = 0
69
+ self.next_initial_actions = None
70
+
71
+ def reset(self):
72
+ """Reset policy state."""
73
+ self.policy.reset()
74
+ self.last_actions = None
75
+ self.action_index = 0
76
+ self.step_count = 0
77
+ self.prediction_count = 0
78
+ self.next_initial_actions = None
79
+ self.current_stage = 0
80
+ self.prediction_history.clear()
81
+ logger.info(f"Policy reset - Task ID: {self.task_id}, Action horizon: {self.action_horizon}")
82
+
83
+ def _handle_task_change(self, new_task_id):
84
+ """Handle task ID change by switching checkpoint and resetting state."""
85
+ if self.task_id != new_task_id:
86
+ old_task_id = self.task_id
87
+ self.task_id = new_task_id
88
+
89
+ logger.info(f"🔄 Task change detected: {old_task_id} → {new_task_id} (max stages: {TASK_NUM_STAGES[new_task_id]})")
90
+
91
+ if self.checkpoint_switcher:
92
+ new_policy = self.checkpoint_switcher.get_policy_for_task(new_task_id)
93
+ if new_policy is not self.policy:
94
+ logger.info(f"📦 Switching checkpoint: task {old_task_id} → {new_task_id}")
95
+ self.base_policy = new_policy
96
+ self.policy = new_policy
97
+ self.policy.reset()
98
+
99
+ self.current_stage = 0
100
+ self.prediction_history.clear()
101
+ self.last_actions = None
102
+ self.action_index = 0
103
+ self.next_initial_actions = None
104
+
105
+ def process_obs(self, obs: dict) -> dict:
106
+ """Process observation to match model input format."""
107
+ prop_state = obs["robot_r1::proprio"]
108
+
109
+ head_original = obs["robot_r1::robot_r1:zed_link:Camera:0::rgb"][..., :3]
110
+ left_original = obs["robot_r1::robot_r1:left_realsense_link:Camera:0::rgb"][..., :3]
111
+ right_original = obs["robot_r1::robot_r1:right_realsense_link:Camera:0::rgb"][..., :3]
112
+
113
+ # Resize images
114
+ head_resized = resize_with_pad(head_original, RESIZE_SIZE, RESIZE_SIZE)
115
+ left_resized = resize_with_pad(left_original, RESIZE_SIZE, RESIZE_SIZE)
116
+ right_resized = resize_with_pad(right_original, RESIZE_SIZE, RESIZE_SIZE)
117
+
118
+ return {
119
+ "observation/egocentric_camera": head_resized,
120
+ "observation/wrist_image_left": left_resized,
121
+ "observation/wrist_image_right": right_resized,
122
+ "observation/state": prop_state,
123
+ "prompt": self.text_prompt,
124
+ }
125
+
126
+ def update_current_stage(self, predicted_subtask_logits):
127
+ """Update current stage using majority voting."""
128
+ if self.task_id is None:
129
+ return
130
+
131
+ max_stage = TASK_NUM_STAGES[self.task_id] - 1
132
+ predicted_stage = int(np.argmax(predicted_subtask_logits))
133
+
134
+ if predicted_stage > max_stage:
135
+ predicted_stage = max_stage
136
+
137
+ self.prediction_history.append(predicted_stage)
138
+
139
+ if len(self.prediction_history) == self.config.history_len:
140
+ next_stage = self.current_stage + 1
141
+
142
+ if next_stage <= max_stage:
143
+ votes_for_next = sum(1 for pred in self.prediction_history if pred == next_stage)
144
+ votes_to_skip = sum(1 for pred in self.prediction_history if pred == next_stage + 1)
145
+ votes_to_go_back = sum(1 for pred in self.prediction_history if pred == self.current_stage - 1)
146
+
147
+ if votes_for_next >= self.config.votes_to_promote:
148
+ old_stage = self.current_stage
149
+ self.current_stage = next_stage
150
+ self.prediction_history.clear()
151
+ logger.info(f"⬆️ Stage advanced: {old_stage} → {self.current_stage} (task {self.task_id}, step {self.step_count})")
152
+ elif votes_to_skip == self.config.history_len:
153
+ old_stage = self.current_stage
154
+ self.current_stage = next_stage
155
+ self.prediction_history.clear()
156
+ logger.info(f"⏭️ Stage skipped: {old_stage} → {self.current_stage} (task {self.task_id}, step {self.step_count})")
157
+ elif votes_to_go_back == self.config.history_len and self.current_stage > 0:
158
+ old_stage = self.current_stage
159
+ self.current_stage -= 1
160
+ self.prediction_history.clear()
161
+ logger.info(f"⬅️ Stage went back: {old_stage} → {self.current_stage} (task {self.task_id}, step {self.step_count})")
162
+
163
+ def prepare_batch_for_pi_behavior(self, batch):
164
+ """Prepare batch for PI_BEHAVIOR model by adding task_id and current_stage."""
165
+ task_id = self.task_id if self.task_id is not None else -1
166
+ batch_copy = batch.copy()
167
+ if "prompt" in batch_copy:
168
+ del batch_copy["prompt"]
169
+
170
+ batch_copy["tokenized_prompt"] = np.array([task_id, self.current_stage], dtype=np.int32)
171
+ batch_copy["tokenized_prompt_mask"] = np.array([True, True], dtype=bool)
172
+ batch_copy["subtask_state"] = np.array(self.current_stage, dtype=np.int32)
173
+
174
+ return batch_copy
175
+
176
+ def _interpolate_actions(self, actions, target_steps):
177
+ """Interpolate actions using cubic spline."""
178
+ from scipy.interpolate import interp1d
179
+
180
+ original_indices = np.linspace(0, len(actions)-1, len(actions))
181
+ target_indices = np.linspace(0, len(actions)-1, target_steps)
182
+
183
+ interpolated = np.zeros((target_steps, actions.shape[1]))
184
+ for dim in range(actions.shape[1]):
185
+ f = interp1d(original_indices, actions[:, dim], kind='cubic')
186
+ interpolated[:, dim] = f(target_indices)
187
+
188
+ return interpolated
189
+
190
+ def act(self, obs: dict) -> torch.Tensor:
191
+ """Main action function."""
192
+
193
+ # Extract task_id from observations
194
+ if "task_id" in obs:
195
+ new_task_id = int(obs["task_id"][0])
196
+ self._handle_task_change(new_task_id)
197
+
198
+ raw_state = obs["robot_r1::proprio"]
199
+ current_state = extract_state_from_proprio(raw_state)
200
+
201
+ # Check if we need new actions
202
+ if self.last_actions is None or self.action_index >= self.config.execute_in_n_steps:
203
+
204
+ # Process observation
205
+ model_input = self.process_obs(obs)
206
+ model_input = self.prepare_batch_for_pi_behavior(model_input)
207
+
208
+ # Add rolling inpainting if available
209
+ if self.next_initial_actions is not None and ("initial_actions" not in model_input or model_input["initial_actions"] is None):
210
+ model_input["initial_actions"] = self.next_initial_actions
211
+
212
+ # Get prediction
213
+ if "initial_actions" in model_input and model_input["initial_actions"] is not None:
214
+ output = self.policy.infer(model_input, initial_actions=model_input["initial_actions"])
215
+ else:
216
+ output = self.policy.infer(model_input)
217
+
218
+ actions = output["actions"]
219
+
220
+ # Ensure correct shape
221
+ if len(actions.shape) == 3:
222
+ actions = actions[0]
223
+ if actions.shape[1] > 23:
224
+ actions = actions[:, :23]
225
+
226
+ # Apply eval tricks if enabled
227
+ should_compress = self.config.execute_in_n_steps < self.config.actions_to_execute
228
+
229
+ if self.config.apply_eval_tricks:
230
+ if self.task_id is not None:
231
+ actions_before = actions.copy()
232
+ actions, corrected_stage = apply_correction_rules(
233
+ self.task_id, self.current_stage, current_state, actions
234
+ )
235
+
236
+ # Log if stage was corrected
237
+ if corrected_stage != self.current_stage:
238
+ logger.info(f"🔧 Correction rule: Stage corrected {self.current_stage} → {corrected_stage} (task {self.task_id}, step {self.step_count})")
239
+ self.current_stage = corrected_stage
240
+ self.prediction_history.clear()
241
+
242
+ # Log if actions were modified
243
+ if not np.allclose(actions_before, actions, rtol=1e-3):
244
+ max_diff = np.max(np.abs(actions_before - actions))
245
+ logger.info(f"🔧 Correction rule: Actions modified (max diff: {max_diff:.4f}, task {self.task_id}, stage {self.current_stage})")
246
+
247
+ if should_compress:
248
+ has_high_variation, mean_var, max_var = check_gripper_variation(
249
+ actions, self.config.actions_to_execute
250
+ )
251
+ if has_high_variation:
252
+ should_compress = False
253
+ logger.info(f"🔧 Gripper variation: Compression disabled (mean: {mean_var:.4f}, max: {max_var:.4f})")
254
+
255
+ # Determine execution parameters
256
+ actions_to_execute = self.config.actions_to_execute if should_compress else self.config.execute_in_n_steps
257
+ execute_steps = self.config.execute_in_n_steps
258
+
259
+ # Save actions for next inpainting (before compression)
260
+ inpainting_start = actions_to_execute
261
+ inpainting_end = inpainting_start + self.config.actions_to_keep
262
+
263
+ if len(actions) >= inpainting_end:
264
+ self.next_initial_actions = actions[inpainting_start:inpainting_end].copy()
265
+ else:
266
+ self.next_initial_actions = None
267
+
268
+ # Extract and compress actions
269
+ self.last_actions = actions[:actions_to_execute].copy()
270
+
271
+ if should_compress:
272
+ compressed_actions = self._interpolate_actions(self.last_actions, execute_steps)
273
+ compression_factor = actions_to_execute / execute_steps
274
+ compressed_actions[:, :3] *= compression_factor # Scale velocities
275
+ self.last_actions = compressed_actions
276
+
277
+ self.action_index = 0
278
+ self.prediction_count += 1
279
+
280
+ # Log prediction details (at lower frequency, every 10 predictions)
281
+ if self.prediction_count % 10 == 0:
282
+ compression_status = f"compressed {actions_to_execute}→{execute_steps}" if should_compress else f"uncompressed ({execute_steps})"
283
+ logger.info(f"🎯 Prediction #{self.prediction_count} | Actions: {compression_status} | Inpainting: {self.next_initial_actions is not None}")
284
+
285
+ # Update stage based on model predictions
286
+ if "subtask_logits" in output:
287
+ self.update_current_stage(output["subtask_logits"])
288
+
289
+ # Get current action from sequence
290
+ if self.action_index >= len(self.last_actions):
291
+ self.action_index = 0
292
+
293
+ current_action = self.last_actions[self.action_index]
294
+ self.action_index += 1
295
+ self.step_count += 1
296
+
297
+ # Log progress every 100 steps
298
+ if self.step_count % 100 == 0:
299
+ logger.info(f"📊 Step {self.step_count} | Task: {self.task_id} | Stage: {self.current_stage}/{TASK_NUM_STAGES[self.task_id]-1} | Predictions: {self.prediction_count}")
300
+
301
+ # Convert to torch tensor
302
+ action_tensor = torch.from_numpy(current_action).float()
303
+ if len(action_tensor) > 23:
304
+ action_tensor = action_tensor[:23]
305
+
306
+ return action_tensor
307
+
legacy/vggt_newbank_boxing_gloves_step19999/code/reference/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/vggt_newbank_boxing_gloves_step19999/code/reference/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/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_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/vggt_newbank_boxing_gloves_step19999/code/src/b1k/training/b1k_da3.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
16
+ import numpy as np
17
+
18
+ from b1k.training.b1k_2026 import BehaviorV3Dataset, B1kInputs2026
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ FOCAL_RATIO = 17.0 / 20.995 # OmniGibson VisionSensor default focal/aperture
23
+
24
+ # dst rgb key -> pose parquet column (same camera)
25
+ POSE_COLS = {
26
+ "observation.images.rgb.head": "observation.robot2cam_pose.zed_link_camera_0",
27
+ "observation.images.rgb.left_wrist": "observation.robot2cam_pose.left_realsense_link_camera_0",
28
+ "observation.images.rgb.right_wrist": "observation.robot2cam_pose.right_realsense_link_camera_0",
29
+ }
30
+ # view order MUST match the bank builder: 0=main(head), 1=left, 2=right
31
+ VIEW_ORDER = (
32
+ "observation.images.rgb.head",
33
+ "observation.images.rgb.left_wrist",
34
+ "observation.images.rgb.right_wrist",
35
+ )
36
+
37
+
38
+ def quat_wxyz_to_R(q):
39
+ w, x, y, z = q / (np.linalg.norm(q) + 1e-12)
40
+ return np.array([
41
+ [1 - 2 * (y * y + z * z), 2 * (x * y - w * z), 2 * (x * z + w * y)],
42
+ [2 * (x * y + w * z), 1 - 2 * (x * x + z * z), 2 * (y * z - w * x)],
43
+ [2 * (x * z - w * y), 2 * (y * z + w * x), 1 - 2 * (x * x + y * y)],
44
+ ])
45
+
46
+
47
+ # OmniGibson cameras use the OpenGL optical convention (-Z forward, +Y up); the DA3 pinhole
48
+ # projection assumes OpenCV (+Z forward, +Y down). This diag(1,-1,-1) flips the camera Y/Z axes.
49
+ # WITHOUT it, cross-view GT-depth reprojection is 0.00 (cameras point the wrong way); WITH it, 0.20+
50
+ # (best of all 8 conventions), and the head camera lands at its true +1.56 m height. (calib/calibrate_v3.py)
51
+ _GL2CV = np.diag([1.0, -1.0, -1.0, 1.0]).astype(np.float32)
52
+
53
+
54
+ def pose7_to_robot2cam(p7: np.ndarray) -> np.ndarray:
55
+ """[pos3, quat_wxyz] camera-pose-in-robot-frame -> 4x4 robot->cam (OpenCV).
56
+
57
+ Convention (validated in calib/): quat=wxyz, pose is the CAMERA-IN-ROBOT transform so
58
+ robot->cam = inv(T), then GL->CV optical flip.
59
+ """
60
+ T = np.eye(4, dtype=np.float32)
61
+ T[:3, :3] = quat_wxyz_to_R(np.asarray(p7[3:], np.float64))
62
+ T[:3, 3] = p7[:3]
63
+ return (_GL2CV @ np.linalg.inv(T)).astype(np.float32)
64
+
65
+
66
+ class BehaviorV3DA3Dataset(BehaviorV3Dataset):
67
+ """BehaviorV3Dataset + DA3 inputs (frames @ da3_hw, extrinsics, intrinsics, task language)."""
68
+
69
+ def __init__(self, *args, da3_hw=(252, 252), lang_cache: str | None = None, lang_max_len: int = 32, **kwargs):
70
+ super().__init__(*args, **kwargs)
71
+ self._da3_hw = tuple(da3_hw)
72
+ self._lang_max_len = int(lang_max_len)
73
+ self._lang = None
74
+ if lang_cache:
75
+ with open(lang_cache, "rb") as f:
76
+ self._lang = pickle.load(f)
77
+ logger.info("DA3 lang cache: %d tasks from %s", len(self._lang), lang_cache)
78
+
79
+ def _episode_poses(self, rec):
80
+ """Cached per-episode pose table (the base reader's parquet cache omits pose columns)."""
81
+ import pandas as pd
82
+ if not hasattr(self, "_pose_cache"):
83
+ from collections import OrderedDict
84
+ self._pose_cache = OrderedDict()
85
+ key = rec["data"]
86
+ if key not in self._pose_cache:
87
+ df = pd.read_parquet(key, columns=["episode_index", "frame_index", *POSE_COLS.values()])
88
+ self._pose_cache[key] = df
89
+ if len(self._pose_cache) > 8:
90
+ self._pose_cache.popitem(last=False)
91
+ df = self._pose_cache[key]
92
+ return df[df["episode_index"] == rec["episode_index"]].sort_values("frame_index")
93
+
94
+ def _decode_native(self, path: str, ts: float):
95
+ """Decode ONE frame at NATIVE resolution, cached by (path, frame_idx). Returns (HWC uint8, native_w).
96
+ Shared by the base (224) and DA3 (252) decode so each frame is decoded ONCE, not twice.
97
+ Enabled only when B1K_SHARED_DECODE=1 (default off => original two-decode behavior)."""
98
+ if not hasattr(self, "_native_cache"):
99
+ from collections import OrderedDict
100
+ self._native_cache = OrderedDict()
101
+ fidx = int(round(ts * self.fps))
102
+ key = (path, fidx)
103
+ hit = self._native_cache.get(key)
104
+ if hit is not None:
105
+ self._native_cache.move_to_end(key)
106
+ return hit
107
+ container = self._cached_container(path)
108
+ vs = container.streams.video[0]
109
+ container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
110
+ frame = None
111
+ for fr in container.decode(vs):
112
+ if fr.time is not None and fr.time >= ts - 1e-3:
113
+ frame = fr
114
+ break
115
+ if frame is None:
116
+ container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
117
+ for fr in container.decode(vs):
118
+ frame = fr
119
+ out = (frame.to_ndarray(format="rgb24"), int(frame.width)) # native HWC uint8, no resize
120
+ self._native_cache[key] = out
121
+ if len(self._native_cache) > 12: # a few cams x a couple frames in flight
122
+ self._native_cache.popitem(last=False)
123
+ return out
124
+
125
+ def _decode_rgb(self, path: str, ts: float) -> np.ndarray:
126
+ """Base VLM (224) frame. With shared-decode, resize from the single native decode (no 2nd decode)."""
127
+ if os.environ.get("B1K_SHARED_DECODE") != "1":
128
+ return super()._decode_rgb(path, ts)
129
+ import cv2
130
+ native, _ = self._decode_native(path, ts)
131
+ r = self._decode_resize
132
+ return cv2.resize(native, (r, r), interpolation=cv2.INTER_AREA) if r > 0 else native
133
+
134
+ def _decode_da3(self, path: str, ts: float) -> np.ndarray:
135
+ """Decode one frame at DA3 resolution (252). HWC uint8 + native width (for intrinsics)."""
136
+ if os.environ.get("B1K_SHARED_DECODE") == "1":
137
+ import cv2
138
+ native, native_w = self._decode_native(path, ts) # reuses the base decode (no 2nd decode)
139
+ h, w = self._da3_hw
140
+ return cv2.resize(native, (w, h), interpolation=cv2.INTER_AREA), native_w
141
+ container = self._cached_container(path)
142
+ vs = container.streams.video[0]
143
+ container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
144
+ frame = None
145
+ for fr in container.decode(vs):
146
+ if fr.time is not None and fr.time >= ts - 1e-3:
147
+ frame = fr
148
+ break
149
+ if frame is None:
150
+ container.seek(int(max(0.0, ts) / vs.time_base), stream=vs, backward=True)
151
+ for fr in container.decode(vs):
152
+ frame = fr
153
+ h, w = self._da3_hw
154
+ native_w = frame.width
155
+ frame = frame.reformat(width=w, height=h, format="rgb24")
156
+ img = frame.to_ndarray(format="rgb24")
157
+ return img, native_w
158
+
159
+ def _lang_entry(self, task_name: str):
160
+ if self._lang is None:
161
+ L = self._lang_max_len
162
+ return np.zeros((L, 1024), np.float32), np.zeros((L,), bool)
163
+ feat, mask = self._lang[task_name]
164
+ return np.asarray(feat, np.float32), np.asarray(mask, bool)
165
+
166
+ def da3_fields(self, i):
167
+ """Compute ONLY the DA3 input fields for sample i (attached AFTER the transform stack,
168
+ which constructs fresh dicts and would drop unknown keys)."""
169
+ item = {}
170
+ ei, t = self.samples[i]
171
+ rec = self.episodes[ei]
172
+ sub = self._episode_poses(rec)
173
+
174
+ h, w = self._da3_hw
175
+ imgs, extr, intr = [], [], []
176
+ for dst in VIEW_ORDER:
177
+ frame_ts = rec["from_ts"][dst] + t / self.fps
178
+ img, native_w = self._decode_da3(rec["video"][dst], frame_ts)
179
+ imgs.append(img)
180
+ p7 = np.asarray(sub[POSE_COLS[dst]].iloc[t], np.float64)
181
+ extr.append(pose7_to_robot2cam(p7))
182
+ f_native = FOCAL_RATIO * native_w
183
+ # native (square) -> da3_hw rescale: fx,cx scale by w/native_w; fy,cy by h/native_h(=native_w)
184
+ K = np.array([
185
+ [f_native * w / native_w, 0, (native_w / 2) * w / native_w],
186
+ [0, f_native * h / native_w, (native_w / 2) * h / native_w],
187
+ [0, 0, 1],
188
+ ], np.float32)
189
+ intr.append(K)
190
+
191
+ item["da3_images"] = np.stack(imgs, 0) # [V,252,252,3] uint8
192
+ item["camera_extrinsics"] = np.stack(extr, 0) # [V,4,4] robot->cam OpenCV
193
+ item["camera_intrinsics"] = np.stack(intr, 0) # [V,3,3] @ da3_hw
194
+ lf, lm = self._lang_entry(rec["task0"])
195
+ item["lang_feat"] = lf
196
+ item["lang_mask"] = lm
197
+ return item
198
+
199
+
200
+ class _AttachDA3Fields:
201
+ """Wraps the TRANSFORMED dataset; merges the raw dataset's DA3 fields into each sample."""
202
+
203
+ def __init__(self, transformed, raw: BehaviorV3DA3Dataset):
204
+ self._transformed = transformed
205
+ self._raw = raw
206
+
207
+ def __len__(self):
208
+ return len(self._transformed)
209
+
210
+ def __getitem__(self, i):
211
+ out = dict(self._transformed[i])
212
+ out.update(self._raw.da3_fields(i))
213
+ return out
214
+
215
+
216
+ def create_v3_behavior_da3_loader(config, root_2026, activities, task_data_json, *,
217
+ lang_cache, sharding=None, shuffle=True,
218
+ num_workers=None, seed=0, da3_hw=(252, 252)):
219
+ """v3 loader with DA3 inputs + a per-batch frozen DA3-GIANT extraction hook (GPU)."""
220
+ import jax
221
+ import dataclasses as _dc
222
+ from b1k.policies import b1k_policy
223
+ from b1k.training.data_loader import transform_dataset, DataLoaderImpl
224
+ from openpi.training.data_loader import TorchDataLoader
225
+ from b1k.training import da3_extractor as _ex
226
+
227
+ data_config = config.data.create(config.assets_dirs, config.model)
228
+ new_inputs = tuple(
229
+ B1kInputs2026(model_type=config.model.model_type)
230
+ if isinstance(x, b1k_policy.B1kInputs) else x
231
+ for x in data_config.data_transforms.inputs
232
+ )
233
+ data_config = _dc.replace(
234
+ data_config, data_transforms=_dc.replace(data_config.data_transforms, inputs=new_inputs))
235
+
236
+ # VGGT-Omega extractor: decode/patchify at process_res (patch 16), not the DA3 252 grid.
237
+ _use_vggt = os.environ.get("USE_VGGT") == "1"
238
+ if _use_vggt:
239
+ da3_hw = (int(os.environ.get("VGGT_PROCESS_RES", "256")),) * 2
240
+
241
+ ds = BehaviorV3DA3Dataset(
242
+ root_2026, activities=activities, action_horizon=config.model.action_horizon,
243
+ task_data_json=task_data_json, seed=seed,
244
+ da3_hw=da3_hw, lang_cache=lang_cache,
245
+ lang_max_len=config.model.da3.lang_max_len,
246
+ )
247
+ tds = transform_dataset(ds, data_config)
248
+ tds = _AttachDA3Fields(tds, ds)
249
+
250
+ logger.info("Building inline DA3-GIANT extractor (da3_hw=%s) ...", da3_hw)
251
+ # Extraction devices: default single-GPU (cuda:0). Set B1K_EXTRACT_DEVICES to spread the frozen
252
+ # DA3-GIANT forward across GPUs (one replica per device, batch split, run concurrently) so the
253
+ # ~2.7s single-GPU extraction shrinks and better overlaps the JAX train step.
254
+ _dev_env = os.environ.get("B1K_EXTRACT_DEVICES", "").strip()
255
+ _devices = [d.strip() for d in _dev_env.split(",") if d.strip()] or None
256
+ _fchunk = int(os.environ.get("B1K_DA3_FWD_CHUNK", "16"))
257
+ logger.info("DA3 extractor: devices=%s forward_chunk=%d", _devices or ["cuda:0"], _fchunk)
258
+ if _use_vggt:
259
+ from b1k.training import vggt_extractor as _vex
260
+ extractor = _vex.VGGTInlineExtractor(process_res=da3_hw[0], forward_chunk=_fchunk, devices=_devices)
261
+ logger.info("Using VGGT-Omega extractor (process_res=%d, grid=%d)", da3_hw[0], da3_hw[0] // 16)
262
+ else:
263
+ extractor = _ex.DA3InlineExtractor(da3_hw=da3_hw, forward_chunk=_fchunk, devices=_devices)
264
+
265
+ # DLPack GPU->GPU handoff: skip the ~2.6s/batch host round-trip by moving extractor features
266
+ # straight from the extraction GPUs to the training GPUs over NVLink. Requires CUDA extraction
267
+ # devices whose count matches the training mesh size (contiguous batch split aligns 1:1).
268
+ # Holds the last few batches' torch source shards alive so the async NVLink copies (device_put)
269
+ # can never read freed memory — replaces a blocking block_until_ready that serialized the producer.
270
+ import collections as _collections
271
+ _keepalive = _collections.deque(maxlen=4)
272
+
273
+ def _dlpack_ok():
274
+ try:
275
+ m = getattr(sharding, "mesh", None)
276
+ return (os.environ.get("B1K_DLPACK") == "1" and m is not None
277
+ and len(list(m.devices.flat)) == len(extractor.devices)
278
+ and all(str(d).startswith("cuda") for d in extractor.devices))
279
+ except Exception:
280
+ return False
281
+
282
+ # Output field order MUST match the extractor's extract()/extract_shards_torch() tuple order.
283
+ _field_names = (["da3_features", "da3_ray", "da3_depth",
284
+ "da3_depth_conf", "da3_pose_enc", "da3_cam_tokens"] if _use_vggt
285
+ else ["da3_features", "da3_ray", "da3_depth"])
286
+
287
+ def batch_transform(batch):
288
+ if _dlpack_ok():
289
+ import jax
290
+ parts = extractor.extract_shards_torch(
291
+ batch["da3_images"], batch["camera_extrinsics"], batch["camera_intrinsics"])
292
+ tdevs = list(sharding.mesh.devices.flat) # training devices, batch-chunk k -> tdevs[k]
293
+
294
+ def _asm(fi): # assemble per-shard torch tensors (field fi) into one sharded jax array
295
+ js = [jax.device_put(jax.dlpack.from_dlpack(parts[k][fi]), tdevs[k]) for k in range(len(parts))]
296
+ gshape = (sum(int(s.shape[0]) for s in js),) + tuple(int(d) for d in js[0].shape[1:])
297
+ return jax.make_array_from_single_device_arrays(gshape, sharding, js)
298
+
299
+ for fi, nm in enumerate(_field_names):
300
+ batch[nm] = _asm(fi)
301
+ # Do NOT block here: the device_put queues behind the in-flight train step on the target
302
+ # GPUs, so blocking would serialize the producer with training (killing the overlap).
303
+ # Instead keep the torch source shards referenced for a few batches so the async NVLink
304
+ # copy can't read freed memory.
305
+ _keepalive.append(parts)
306
+ else:
307
+ outs = extractor.extract(
308
+ batch["da3_images"], batch["camera_extrinsics"], batch["camera_intrinsics"])
309
+ for nm, arr in zip(_field_names, outs):
310
+ batch[nm] = arr # da3_features = uint16 bf16-bits; rest fp32
311
+ batch.pop("da3_images", None)
312
+ batch.pop("camera_intrinsics", None)
313
+ return batch
314
+
315
+ loader = TorchDataLoader(
316
+ tds, local_batch_size=config.batch_size // jax.process_count(),
317
+ sharding=sharding, shuffle=shuffle,
318
+ num_workers=config.num_workers if num_workers is None else num_workers,
319
+ seed=seed, batch_transform=batch_transform,
320
+ )
321
+ return DataLoaderImpl(data_config, loader)
legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/training/vggt_extractor.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Inline VGGT-Omega feature/geometry extractor (PyTorch, runs in the openpi venv).
2
+
3
+ Drop-in alternative to DA3InlineExtractor: same replicated-per-GPU design and the same
4
+ extract()/extract_shards_torch() batch-splitting, but produces VGGT-Omega's richer outputs.
5
+
6
+ Per forward we harvest EVERYTHING VGGT-Omega gives us and map it into the spatial bank:
7
+ - feats [B,4,V,2048,gh,gw] 4 aggregator taps (blocks 4/11/17/23), patch tokens only
8
+ - ray [B,V,3,gh,gw] ANALYTIC camera-frame unit ray dirs from the intrinsics
9
+ (VGGT has no ray output; K^-1[u,v,1] is exact, better than a
10
+ predicted ray). world lift happens in the bank via extrinsics.
11
+ - depth [B,V,1,gh,gw] VGGT dense_head depth, pooled to the patch grid
12
+ - depth_conf [B,V,1,gh,gw] VGGT dense_head confidence (per-patch geometric reliability)
13
+ - pose_enc [B,V,9] VGGT camera_head pose encoding (trans3+quat4+fov2)
14
+ - cam_tokens [B,V,17,2048] camera(1)+register(16) global tokens
15
+
16
+ Field order for extract()/extract_shards_torch(): (feats, ray, depth, depth_conf, pose_enc, cam_tokens).
17
+ feats ship as bf16 BITS (uint16) like the DA3 path; the rest are fp32.
18
+ """
19
+
20
+ import concurrent.futures
21
+ import contextlib
22
+ import os
23
+ import sys
24
+
25
+ import numpy as np
26
+ import torch
27
+
28
+ try:
29
+ torch.set_num_threads(1)
30
+ torch.set_num_interop_threads(1)
31
+ except Exception: # noqa: BLE001
32
+ pass
33
+
34
+ # Vendored VGGT-Omega package (uses absolute `from vggt_omega...` imports internally).
35
+ _VENDOR = "/work/jack/da3xvla_src/DA3-XVLA/third_party"
36
+ if _VENDOR not in sys.path:
37
+ sys.path.insert(0, _VENDOR)
38
+
39
+
40
+ def _to_dev(a, device, dtype=None):
41
+ if isinstance(a, torch.Tensor):
42
+ t = a.to(device, non_blocking=True)
43
+ return t.to(dtype) if dtype is not None and t.dtype != dtype else t
44
+ return torch.as_tensor(a, device=device, dtype=dtype)
45
+
46
+
47
+ class VGGTInlineExtractor:
48
+ """Frozen VGGT-Omega-1B multi-view extractor, REPLICATED one-per-GPU (mirrors DA3InlineExtractor)."""
49
+
50
+ CACHED_LAYERS = (4, 11, 17, 23) # aggregator.cached_layer_indices; 4 taps -> num_layers=4
51
+ PATCH_START = 17 # 1 camera + 16 register tokens
52
+ OUT_CHANNELS = 2048 # 2 * embed_dim (frame + inter-frame concat)
53
+
54
+ def __init__(
55
+ self,
56
+ model_name: str = "JackLiu0406/vggt-omega-1b",
57
+ ckpt_filename: str = "vggt_omega_1b_512.pt",
58
+ process_res: int = 256,
59
+ devices=None,
60
+ forward_chunk: int = 8,
61
+ ):
62
+ from vggt_omega.models.vggt_omega import VGGTOmega
63
+ from huggingface_hub import hf_hub_download
64
+
65
+ if int(process_res) % 16 != 0:
66
+ raise ValueError(f"process_res={process_res} must be a multiple of patch_size=16")
67
+ self.process_res = int(process_res)
68
+ self.grid = self.process_res // 16
69
+ if devices is None:
70
+ devices = ["cuda:0" if torch.cuda.is_available() else "cpu"]
71
+ self.devices = list(devices)
72
+ self.forward_chunk = int(forward_chunk)
73
+
74
+ ckpt_path = hf_hub_download(repo_id=model_name, filename=ckpt_filename,
75
+ cache_dir=os.environ.get("HF_HOME", None))
76
+ sd = torch.load(ckpt_path, map_location="cpu", weights_only=False)
77
+ if isinstance(sd, dict) and "state_dict" in sd:
78
+ sd = sd["state_dict"]
79
+
80
+ self.replicas = []
81
+ for dev in self.devices:
82
+ m = VGGTOmega(patch_size=16, embed_dim=1024,
83
+ enable_camera=True, enable_depth=True, enable_alignment=False)
84
+ m.load_state_dict(sd, strict=False) # heads present; drops text_alignment_head keys
85
+ m = m.to(dev).eval()
86
+ for p in m.parameters():
87
+ p.requires_grad_(False)
88
+ self.replicas.append(m)
89
+ del sd # free the ~4GB host checkpoint copy before the DataLoader spawns its worker processes
90
+ self._pool = concurrent.futures.ThreadPoolExecutor(max_workers=max(1, len(self.devices)))
91
+ self._warmup()
92
+
93
+ def _warmup(self):
94
+ """Run one forward per replica SINGLE-THREADED so every lazy op wrapper is initialized before
95
+ the concurrent per-device extraction threads run (avoids first-call races across threads)."""
96
+ V = 3
97
+ R = self.process_res
98
+ img = np.zeros((1, V, R, R, 3), np.uint8)
99
+ ext = np.tile(np.eye(4, dtype=np.float32), (1, V, 1, 1))
100
+ f = 0.9 * R
101
+ K = np.tile(np.array([[f, 0, R / 2], [0, f, R / 2], [0, 0, 1]], np.float32), (1, V, 1, 1))
102
+ for di in range(len(self.devices)):
103
+ self._run_shard(di, img, ext, K, return_torch=False)
104
+
105
+ def _preprocess(self, images: np.ndarray, device) -> torch.Tensor:
106
+ """images [B,V,H,W,3] uint8/float -> [B,V,3,process_res,process_res] in [0,1] (aggregator renorms)."""
107
+ x = _to_dev(images, device)
108
+ if x.dtype == torch.uint8:
109
+ x = x.float() / 255.0
110
+ elif x.max() > 1.5:
111
+ x = x.float() / 255.0
112
+ x = x.permute(0, 1, 4, 2, 3) # [B,V,3,H,W]
113
+ b, v = x.shape[:2]
114
+ x = torch.nn.functional.interpolate(
115
+ x.flatten(0, 1), size=(self.process_res, self.process_res),
116
+ mode="bilinear", align_corners=False,
117
+ ).view(b, v, 3, self.process_res, self.process_res)
118
+ return x.clamp_(0.0, 1.0)
119
+
120
+ def _rays_from_intrinsics(self, intrinsics: np.ndarray, src_hw, device) -> torch.Tensor:
121
+ """intrinsics [B,V,3,3] at src_hw -> camera-frame unit ray dirs [B,V,3,grid,grid] (row-major).
122
+ Analytic pinhole back-projection: dir = normalize([(x-cx)/fx, (y-cy)/fy, 1]). Deliberately NO
123
+ torch.linalg.inv -- its lazy wrapper races across the per-device extraction THREADS on first use
124
+ ('lazy wrapper should be called at most once'); the pinhole inverse is closed-form anyway."""
125
+ g = self.grid
126
+ K = _to_dev(intrinsics, device, torch.float32) # [B,V,3,3] at src_hw
127
+ sh, sw = src_hw
128
+ rw, rh = g / sw, g / sh # rescale K from src pixels to the g x g grid
129
+ fx = (K[..., 0, 0] * rw)[..., None, None] # [B,V,1,1]
130
+ cx = (K[..., 0, 2] * rw)[..., None, None]
131
+ fy = (K[..., 1, 1] * rh)[..., None, None]
132
+ cy = (K[..., 1, 2] * rh)[..., None, None]
133
+ ys, xs = torch.meshgrid(torch.arange(g, device=device, dtype=torch.float32) + 0.5,
134
+ torch.arange(g, device=device, dtype=torch.float32) + 0.5,
135
+ indexing="ij") # [g,g] row-major: rows=y=h, cols=x=w (matches 'b (h w)')
136
+ dx = (xs[None, None] - cx) / fx # [B,V,g,g]
137
+ dy = (ys[None, None] - cy) / fy
138
+ dz = torch.ones_like(dx)
139
+ dirs = torch.stack([dx, dy, dz], dim=2) # [B,V,3,g,g]
140
+ return dirs / dirs.norm(dim=2, keepdim=True).clamp_min(1e-8)
141
+
142
+ def _run_shard(self, di, images, extrinsics, intrinsics, return_torch=False):
143
+ dev = self.devices[di]
144
+ dev_idx = int(dev.split(":")[1]) if ":" in dev else None
145
+ m = self.replicas[di]
146
+ chunk = self.forward_chunk
147
+ g = self.grid
148
+ src_hw = (int(images.shape[2]), int(images.shape[3])) # [B,V,H,W,3]
149
+ acc = [[] for _ in range(6)] # feats, ray, depth, depth_conf, pose_enc, cam_tokens
150
+ ctx = torch.cuda.device(dev_idx) if dev_idx is not None else contextlib.nullcontext()
151
+ with ctx, torch.no_grad():
152
+ for i in range(0, images.shape[0], chunk):
153
+ x = self._preprocess(images[i:i + chunk], dev) # [b,V,3,R,R] in [0,1]
154
+ b, V = x.shape[:2]
155
+ amp = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
156
+ with torch.autocast(device_type="cuda", dtype=amp):
157
+ cached, patch_start = m.aggregator(x) # list; patch_start == 17
158
+ # --- patch feature taps -> [b,4,V,C,g,g] ---
159
+ taps = []
160
+ for li in self.CACHED_LAYERS:
161
+ tok = cached[li] # [b,V,ntok,C]
162
+ pt = tok[:, :, patch_start:, :] # [b,V,g*g,C]
163
+ pt = pt.reshape(b, V, g, g, self.OUT_CHANNELS).permute(0, 1, 4, 2, 3) # [b,V,C,g,g]
164
+ taps.append(pt)
165
+ feats = torch.stack(taps, dim=1).to(torch.bfloat16) # [b,4,V,C,g,g]
166
+ cam_tokens = cached[-1][:, :, :patch_start, :].float() # [b,V,17,C]
167
+ # --- heads (fp32) ---
168
+ with torch.autocast(device_type="cuda", enabled=False):
169
+ pose_enc = m.camera_head(cached, patch_token_start=patch_start).float() # [b,V,9]
170
+ depth, depth_conf = m.dense_head(cached, images=x, patch_token_start=patch_start)
171
+ # depth [b,V,R,R,1], depth_conf [b,V,R,R] -> pool to grid
172
+ depth = depth.float().squeeze(-1) # [b,V,R,R]
173
+ depth = torch.nn.functional.adaptive_avg_pool2d(
174
+ depth.reshape(b * V, 1, *depth.shape[2:]), (g, g)).reshape(b, V, 1, g, g)
175
+ depth_conf = torch.nn.functional.adaptive_avg_pool2d(
176
+ depth_conf.float().reshape(b * V, 1, *depth_conf.shape[2:]), (g, g)).reshape(b, V, 1, g, g)
177
+ ray = self._rays_from_intrinsics(intrinsics[i:i + chunk], src_hw, dev) # [b,V,3,g,g]
178
+ outs = (feats.view(torch.uint16), ray, depth, depth_conf, pose_enc, cam_tokens)
179
+ if return_torch:
180
+ for j, o in enumerate(outs):
181
+ acc[j].append(o)
182
+ else:
183
+ for j, o in enumerate(outs):
184
+ acc[j].append(o.cpu().numpy())
185
+ if return_torch:
186
+ return tuple(torch.cat(a, 0) for a in acc)
187
+ return tuple(np.concatenate(a, 0) for a in acc)
188
+
189
+ def _split_run(self, images, extrinsics, intrinsics, return_torch):
190
+ b = int(images.shape[0]); nd = len(self.devices)
191
+ bounds = [round(i * b / nd) for i in range(nd + 1)]
192
+ futs = {}
193
+ for di in range(nd):
194
+ s, e = bounds[di], bounds[di + 1]
195
+ if s >= e:
196
+ continue
197
+ futs[di] = self._pool.submit(self._run_shard, di, images[s:e], extrinsics[s:e],
198
+ intrinsics[s:e], return_torch)
199
+ return [futs[di].result() for di in sorted(futs)]
200
+
201
+ def extract_shards_torch(self, images, extrinsics, intrinsics):
202
+ """Per-shard torch GPU tuples (feats,ray,depth,depth_conf,pose_enc,cam_tokens), shard k on devices[k]."""
203
+ return self._split_run(images, extrinsics, intrinsics, True)
204
+
205
+ def extract(self, images, extrinsics, intrinsics):
206
+ """Numpy (feats,ray,depth,depth_conf,pose_enc,cam_tokens), batch split across replicas."""
207
+ parts = self._split_run(images, extrinsics, intrinsics, False)
208
+ return tuple(np.concatenate([p[j] for p in parts], axis=0) for j in range(6))
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/__init__.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """VGGT-Omega inference package."""
8
+
9
+ from .models import VGGTOmega
10
+
11
+ __version__ = "0.0.1"
12
+
13
+ __all__ = ["VGGTOmega", "__version__"]
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/__init__.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ from .vggt_omega import VGGTOmega
8
+
9
+ __all__ = ["VGGTOmega"]
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/aggregator.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+
10
+ from vggt_omega.models.layers import Mlp, RopePositionEmbedding, SelfAttentionBlock
11
+ from vggt_omega.models.layers.vision_transformer import DinoVisionTransformer
12
+
13
+
14
+ _RESNET_MEAN = [0.485, 0.456, 0.406]
15
+ _RESNET_STD = [0.229, 0.224, 0.225]
16
+
17
+
18
+ class Aggregator(nn.Module):
19
+ """Alternating-attention encoder over video frames."""
20
+
21
+ def __init__(
22
+ self,
23
+ patch_size: int = 16,
24
+ embed_dim: int = 1024,
25
+ depth: int = 24,
26
+ num_heads: int = 16,
27
+ mlp_ratio: float = 4.0,
28
+ num_register_tokens: int = 16,
29
+ register_attention_block_indices: list[int] = [2, 6, 9, 14, 20],
30
+ cached_layer_indices: tuple[int, ...] = (4, 11, 17, 23),
31
+ ) -> None:
32
+ super().__init__()
33
+
34
+ self.patch_embed = _build_patch_embed(patch_size=patch_size, embed_dim=embed_dim)
35
+ self.rope_embed = RopePositionEmbedding(
36
+ embed_dim=embed_dim,
37
+ num_heads=num_heads,
38
+ base=100,
39
+ normalize_coords="max",
40
+ dtype=torch.float32,
41
+ )
42
+
43
+ self.frame_blocks = nn.ModuleList(
44
+ [
45
+ SelfAttentionBlock(
46
+ dim=embed_dim,
47
+ num_heads=num_heads,
48
+ ffn_ratio=mlp_ratio,
49
+ qkv_bias=True,
50
+ proj_bias=True,
51
+ ffn_bias=True,
52
+ ffn_layer=Mlp,
53
+ init_values=1e-5,
54
+ use_qk_norm=True,
55
+ mask_k_bias=True,
56
+ )
57
+ for _ in range(depth)
58
+ ]
59
+ )
60
+ self.inter_frame_blocks = nn.ModuleList(
61
+ [
62
+ SelfAttentionBlock(
63
+ dim=embed_dim,
64
+ num_heads=num_heads,
65
+ ffn_ratio=mlp_ratio,
66
+ qkv_bias=True,
67
+ proj_bias=True,
68
+ ffn_bias=True,
69
+ ffn_layer=Mlp,
70
+ init_values=1e-5,
71
+ use_qk_norm=True,
72
+ mask_k_bias=True,
73
+ )
74
+ for _ in range(depth)
75
+ ]
76
+ )
77
+
78
+ self.depth = depth
79
+ self.patch_size = patch_size
80
+ self.cached_layer_indices = set(cached_layer_indices)
81
+ self.camera_token = nn.Parameter(torch.empty(1, 2, 1, embed_dim))
82
+ self.register_token = nn.Parameter(torch.empty(1, 2, num_register_tokens, embed_dim))
83
+ self.patch_token_start = 1 + num_register_tokens
84
+
85
+ self.inter_frame_attention_types = ["global"] * depth
86
+ for idx in register_attention_block_indices:
87
+ if idx < 0 or idx >= depth:
88
+ raise ValueError(f"register_attention_block_indices contains invalid block index {idx}")
89
+ self.inter_frame_attention_types[idx] = "register"
90
+
91
+ for name, value in (("_resnet_mean", _RESNET_MEAN), ("_resnet_std", _RESNET_STD)):
92
+ self.register_buffer(name, torch.FloatTensor(value).view(1, 1, 3, 1, 1), persistent=False)
93
+
94
+ self.init_weights()
95
+
96
+ def init_weights(self) -> None:
97
+ nn.init.normal_(self.camera_token, std=1e-3)
98
+ nn.init.normal_(self.register_token, std=1e-3)
99
+
100
+ def forward(
101
+ self,
102
+ images: torch.Tensor,
103
+ ) -> tuple[list[torch.Tensor | None], int]:
104
+ batch_size, num_frames, num_channels, height, width = images.shape
105
+ if num_channels != 3:
106
+ raise ValueError(f"Expected 3 input channels, got {num_channels}")
107
+
108
+ images = (images - self._resnet_mean) / self._resnet_std
109
+ images = images.view(batch_size * num_frames, num_channels, height, width)
110
+
111
+ camera_token = slice_expand_and_flatten(self.camera_token, batch_size, num_frames)
112
+ register_token = slice_expand_and_flatten(self.register_token, batch_size, num_frames)
113
+
114
+ patch_tokens = self.patch_embed(images)
115
+ if isinstance(patch_tokens, dict):
116
+ patch_tokens = patch_tokens["x_norm_patchtokens"]
117
+
118
+ tokens = torch.cat([camera_token, register_token, patch_tokens], dim=1)
119
+ _, num_tokens, embed_dim = tokens.shape
120
+
121
+ patch_grid_size = (height // self.patch_size, width // self.patch_size)
122
+ with torch.no_grad():
123
+ rope_sin, rope_cos = self.rope_embed(H=patch_grid_size[0], W=patch_grid_size[1])
124
+ frame_rope = (
125
+ rope_sin.to(device=patch_tokens.device, dtype=torch.float32),
126
+ rope_cos.to(device=patch_tokens.device, dtype=torch.float32),
127
+ )
128
+
129
+ outputs = []
130
+ for block_idx in range(self.depth):
131
+ tokens, frame_tokens = self._run_frame_block(
132
+ tokens,
133
+ batch_size,
134
+ num_frames,
135
+ num_tokens,
136
+ embed_dim,
137
+ block_idx,
138
+ frame_rope,
139
+ )
140
+ tokens = self._run_inter_frame_attention_block(
141
+ tokens,
142
+ batch_size,
143
+ num_frames,
144
+ num_tokens,
145
+ embed_dim,
146
+ block_idx,
147
+ self.inter_frame_attention_types[block_idx],
148
+ )
149
+ if block_idx in self.cached_layer_indices:
150
+ outputs.append(torch.cat([frame_tokens, tokens], dim=-1))
151
+ else:
152
+ outputs.append(None)
153
+
154
+ return outputs, self.patch_token_start
155
+
156
+ def _run_frame_block(
157
+ self,
158
+ tokens: torch.Tensor,
159
+ batch_size: int,
160
+ num_frames: int,
161
+ num_tokens: int,
162
+ embed_dim: int,
163
+ block_idx: int,
164
+ rope_sincos: tuple[torch.Tensor, torch.Tensor],
165
+ ) -> tuple[torch.Tensor, torch.Tensor]:
166
+ tokens = tokens.view(batch_size * num_frames, num_tokens, embed_dim)
167
+ tokens = self.frame_blocks[block_idx](tokens, rope_sincos)
168
+ return tokens, tokens.view(batch_size, num_frames, num_tokens, embed_dim)
169
+
170
+ def _run_inter_frame_attention_block(
171
+ self,
172
+ tokens: torch.Tensor,
173
+ batch_size: int,
174
+ num_frames: int,
175
+ num_tokens: int,
176
+ embed_dim: int,
177
+ block_idx: int,
178
+ attention_type: str,
179
+ ) -> torch.Tensor:
180
+ tokens = tokens.view(batch_size, num_frames, num_tokens, embed_dim)
181
+
182
+ if attention_type == "global":
183
+ tokens = tokens.view(batch_size, num_frames * num_tokens, embed_dim)
184
+ tokens = self.inter_frame_blocks[block_idx](tokens, None)
185
+ return tokens.view(batch_size, num_frames, num_tokens, embed_dim)
186
+
187
+ if attention_type != "register":
188
+ raise ValueError(f"Unknown inter-frame attention type: {attention_type}")
189
+
190
+ patch_token_start = self.patch_token_start
191
+ camera_and_register_tokens = tokens[:, :, :patch_token_start].reshape(
192
+ batch_size,
193
+ num_frames * patch_token_start,
194
+ embed_dim,
195
+ )
196
+ patch_tokens = tokens[:, :, patch_token_start:].reshape(
197
+ batch_size,
198
+ num_frames * (num_tokens - patch_token_start),
199
+ embed_dim,
200
+ )
201
+
202
+ camera_and_register_tokens = self.inter_frame_blocks[block_idx](camera_and_register_tokens, None)
203
+ tokens = torch.cat([camera_and_register_tokens, patch_tokens], dim=1)
204
+
205
+ camera_and_register_tokens = tokens[:, : num_frames * patch_token_start].view(
206
+ batch_size,
207
+ num_frames,
208
+ patch_token_start,
209
+ embed_dim,
210
+ )
211
+ patch_tokens = tokens[:, num_frames * patch_token_start :].view(
212
+ batch_size,
213
+ num_frames,
214
+ num_tokens - patch_token_start,
215
+ embed_dim,
216
+ )
217
+ return torch.cat([camera_and_register_tokens, patch_tokens], dim=2)
218
+
219
+
220
+ def _build_patch_embed(patch_size: int, embed_dim: int) -> DinoVisionTransformer:
221
+ model = DinoVisionTransformer(
222
+ img_size=224,
223
+ patch_size=patch_size,
224
+ in_chans=3,
225
+ pos_embed_rope_base=100,
226
+ pos_embed_rope_normalize_coords="max",
227
+ pos_embed_rope_dtype="fp32",
228
+ embed_dim=embed_dim,
229
+ depth=24,
230
+ num_heads=16,
231
+ ffn_ratio=4,
232
+ qkv_bias=True,
233
+ drop_path_rate=0.0,
234
+ layerscale_init=1.0e-5,
235
+ norm_layer="layernormbf16",
236
+ ffn_layer="mlp",
237
+ ffn_bias=True,
238
+ proj_bias=True,
239
+ n_storage_tokens=4,
240
+ mask_k_bias=True,
241
+ )
242
+ model.init_weights()
243
+ return model
244
+
245
+
246
+ def slice_expand_and_flatten(token_tensor: torch.Tensor, batch_size: int, num_frames: int) -> torch.Tensor:
247
+ first_frame_token = token_tensor[:, 0:1].expand(batch_size, 1, *token_tensor.shape[2:])
248
+ other_frame_tokens = token_tensor[:, 1:].expand(batch_size, num_frames - 1, *token_tensor.shape[2:])
249
+ tokens = torch.cat([first_frame_token, other_frame_tokens], dim=1)
250
+ return tokens.view(batch_size * num_frames, *tokens.shape[2:])
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ from .camera_head import CameraHead
8
+ from .dense_head import DenseHead
9
+ from .text_alignment_head import TextAlignmentHead
10
+
11
+ __all__ = ["CameraHead", "DenseHead", "TextAlignmentHead"]
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/camera_head.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+
11
+ from vggt_omega.models.layers import SelfAttentionBlock
12
+
13
+
14
+ class CameraHead(nn.Module):
15
+ """Camera head used by the released VGGT-Omega checkpoints."""
16
+
17
+ def __init__(self, dim_in: int = 2048) -> None:
18
+ super().__init__()
19
+
20
+ self.token_norm = nn.LayerNorm(dim_in, eps=1e-5)
21
+ # Head-local transformer blocks that mix camera and register tokens across frames.
22
+ self.trunk = nn.ModuleList(
23
+ [
24
+ SelfAttentionBlock(
25
+ dim=dim_in,
26
+ num_heads=16,
27
+ ffn_ratio=4.0,
28
+ qkv_bias=True,
29
+ proj_bias=True,
30
+ ffn_bias=True,
31
+ init_values=1e-5,
32
+ use_qk_norm=False,
33
+ mask_k_bias=True,
34
+ )
35
+ for _ in range(4)
36
+ ]
37
+ )
38
+ self.trunk_norm = nn.LayerNorm(dim_in, eps=1e-5)
39
+ self.camera_branch = nn.Sequential(
40
+ nn.Linear(dim_in, dim_in // 2, bias=True),
41
+ nn.GELU(),
42
+ nn.Linear(dim_in // 2, 9, bias=True),
43
+ )
44
+
45
+ def forward(
46
+ self,
47
+ aggregated_tokens_list: list[torch.Tensor | None],
48
+ patch_token_start: int,
49
+ ) -> torch.Tensor:
50
+ tokens = aggregated_tokens_list[-1]
51
+ if tokens is None:
52
+ raise ValueError("Aggregator did not cache the final layer, which CameraHead needs.")
53
+ batch_size, num_frames, num_tokens, _ = tokens.shape
54
+
55
+ if patch_token_start is None:
56
+ raise ValueError("patch_token_start is required for CameraHead")
57
+ if patch_token_start > num_tokens:
58
+ raise ValueError(f"patch_token_start ({patch_token_start}) exceeds token length ({num_tokens})")
59
+
60
+ if tokens.dtype != torch.float32:
61
+ tokens = tokens.float()
62
+
63
+ camera_and_register_tokens = tokens[:, :, :patch_token_start]
64
+ camera_and_register_tokens = self.token_norm(camera_and_register_tokens)
65
+
66
+ camera_and_register_tokens = camera_and_register_tokens.reshape(batch_size, num_frames * patch_token_start, -1)
67
+ rope_sincos = None
68
+ for block in self.trunk:
69
+ camera_and_register_tokens = block(camera_and_register_tokens, rope_sincos)
70
+
71
+ camera_and_register_tokens = camera_and_register_tokens.reshape(batch_size, num_frames, patch_token_start, -1)
72
+ camera_tokens = self.trunk_norm(camera_and_register_tokens[:, :, 0])
73
+ return _apply_camera_activation(self.camera_branch(camera_tokens))
74
+
75
+
76
+ def _apply_camera_activation(raw_camera: torch.Tensor) -> torch.Tensor:
77
+ translation = raw_camera[..., :3]
78
+ quaternion = raw_camera[..., 3:7]
79
+ fov = F.relu(raw_camera[..., 7:]) + 0.01
80
+ return torch.cat([translation, quaternion, fov], dim=-1)
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/dense_head.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ # Inspired by https://github.com/DepthAnything/Depth-Anything-V2
8
+
9
+ import math
10
+ from typing import Tuple
11
+
12
+ import torch
13
+ import torch.nn as nn
14
+ import torch.nn.functional as F
15
+
16
+ from .utils import create_uv_grid, position_grid_to_embed
17
+
18
+
19
+ class DenseHead(nn.Module):
20
+ """Dense prediction head used by the released VGGT-Omega checkpoints."""
21
+
22
+ def __init__(
23
+ self,
24
+ dim_in: int = 2048,
25
+ patch_size: int = 16,
26
+ features: int = 256,
27
+ out_channels: list[int] = [256, 512, 1024, 1024],
28
+ intermediate_layer_idx: list[int] = [4, 11, 17, 23],
29
+ ) -> None:
30
+ super().__init__()
31
+
32
+ if patch_size % 4 != 0:
33
+ raise ValueError(
34
+ "DenseHead expects patch_size divisible by 4 because the fused feature is decoded "
35
+ f"from 1/4 scale. Got patch_size={patch_size}."
36
+ )
37
+
38
+ self.patch_size = patch_size
39
+ self.intermediate_layer_idx = intermediate_layer_idx
40
+ self.final_shuffle_factor = patch_size // 4
41
+ self.norm = nn.LayerNorm(dim_in, eps=1e-5)
42
+
43
+ self.projects = nn.ModuleList(
44
+ [nn.Conv2d(in_channels=dim_in, out_channels=oc, kernel_size=1, stride=1, padding=0) for oc in out_channels]
45
+ )
46
+ self.resize_layers = nn.ModuleList(
47
+ [
48
+ _make_dense_resize_layer(channels=out_channels[0], resize_scale=4.0),
49
+ _make_dense_resize_layer(channels=out_channels[1], resize_scale=2.0),
50
+ _make_dense_resize_layer(channels=out_channels[2], resize_scale=1.0),
51
+ _make_dense_resize_layer(channels=out_channels[3], resize_scale=0.5),
52
+ ]
53
+ )
54
+
55
+ self.scratch = _make_scratch(out_channels, features)
56
+ self.scratch.stem_transpose = None
57
+ self.scratch.refinenet1 = _make_fusion_block(features)
58
+ self.scratch.refinenet2 = _make_fusion_block(features)
59
+ self.scratch.refinenet3 = _make_fusion_block(features)
60
+ self.scratch.refinenet4 = _make_fusion_block(features, has_residual=False)
61
+
62
+ self.proj = _make_prediction_head(
63
+ features,
64
+ self.final_shuffle_factor**2,
65
+ )
66
+ self.proj_conf = _make_prediction_head(
67
+ features,
68
+ self.final_shuffle_factor**2,
69
+ )
70
+ _init_small_conf_prediction_head(self.proj_conf)
71
+
72
+ def forward(
73
+ self,
74
+ aggregated_tokens_list: list[torch.Tensor | None],
75
+ images: torch.Tensor,
76
+ patch_token_start: int,
77
+ frames_chunk_size: int | None = 8,
78
+ ) -> tuple[torch.Tensor, torch.Tensor]:
79
+ if patch_token_start is None:
80
+ raise ValueError("patch_token_start is required for DenseHead")
81
+
82
+ _, num_frames, _, _, _ = images.shape
83
+
84
+ if frames_chunk_size is None or frames_chunk_size >= num_frames:
85
+ return self._forward_impl(aggregated_tokens_list, images, patch_token_start)
86
+
87
+ assert frames_chunk_size > 0
88
+
89
+ depth_chunks = []
90
+ depth_conf_chunks = []
91
+ for frames_start_idx in range(0, num_frames, frames_chunk_size):
92
+ frames_end_idx = min(frames_start_idx + frames_chunk_size, num_frames)
93
+ depth_chunk, depth_conf_chunk = self._forward_impl(
94
+ aggregated_tokens_list,
95
+ images,
96
+ patch_token_start,
97
+ frames_start_idx,
98
+ frames_end_idx,
99
+ )
100
+ depth_chunks.append(depth_chunk)
101
+ depth_conf_chunks.append(depth_conf_chunk)
102
+
103
+ return torch.cat(depth_chunks, dim=1), torch.cat(depth_conf_chunks, dim=1)
104
+
105
+ def _forward_impl(
106
+ self,
107
+ aggregated_tokens_list: list[torch.Tensor | None],
108
+ images: torch.Tensor,
109
+ patch_token_start: int,
110
+ frames_start_idx: int | None = None,
111
+ frames_end_idx: int | None = None,
112
+ ) -> tuple[torch.Tensor, torch.Tensor]:
113
+ if frames_start_idx is not None and frames_end_idx is not None:
114
+ images = images[:, frames_start_idx:frames_end_idx].contiguous()
115
+
116
+ batch_size, num_frames, _, height, width = images.shape
117
+ patch_h, patch_w = height // self.patch_size, width // self.patch_size
118
+
119
+ multi_scale_features = []
120
+ for feature_idx, layer_idx in enumerate(self.intermediate_layer_idx):
121
+ x = aggregated_tokens_list[layer_idx]
122
+ if x is None:
123
+ raise ValueError(f"Aggregator did not cache layer {layer_idx}, which DenseHead needs.")
124
+ x = x[:, :, patch_token_start:]
125
+ if frames_start_idx is not None and frames_end_idx is not None:
126
+ x = x[:, frames_start_idx:frames_end_idx]
127
+ if x.dtype != torch.float32:
128
+ x = x.float()
129
+
130
+ x = x.reshape(batch_size * num_frames, -1, x.shape[-1])
131
+ x = self.norm(x)
132
+ x = x.permute(0, 2, 1).reshape((x.shape[0], x.shape[-1], patch_h, patch_w))
133
+ x = self.projects[feature_idx](x)
134
+ x = self._apply_pos_embed(x, width, height)
135
+ x = self.resize_layers[feature_idx](x)
136
+ multi_scale_features.append(x)
137
+
138
+ fused = self.scratch_forward(multi_scale_features)
139
+ fused = self._apply_pos_embed(fused, width, height)
140
+
141
+ depth_logits = self.proj(fused)
142
+ depth_logits = F.pixel_shuffle(depth_logits, self.final_shuffle_factor)
143
+ depth_logits = depth_logits.permute(0, 2, 3, 1)
144
+
145
+ confidence_logits = self.proj_conf(fused)
146
+ confidence_logits = F.pixel_shuffle(confidence_logits, self.final_shuffle_factor)
147
+ confidence_logits = confidence_logits.permute(0, 2, 3, 1).squeeze(-1)
148
+
149
+ depth = torch.exp(depth_logits)
150
+ depth_conf = 1.0 + torch.exp(confidence_logits)
151
+
152
+ depth = depth.view(batch_size, num_frames, *depth.shape[1:])
153
+ depth_conf = depth_conf.view(batch_size, num_frames, *depth_conf.shape[1:])
154
+
155
+ if depth.dtype != torch.float32 or depth_conf.dtype != torch.float32:
156
+ raise TypeError(f"DenseHead outputs must be fp32, got depth={depth.dtype}, conf={depth_conf.dtype}")
157
+
158
+ return depth, depth_conf
159
+
160
+ def _apply_pos_embed(self, x: torch.Tensor, width: int, height: int, ratio: float = 0.1) -> torch.Tensor:
161
+ patch_w = x.shape[-1]
162
+ patch_h = x.shape[-2]
163
+ pos_embed = create_uv_grid(patch_w, patch_h, aspect_ratio=width / height, dtype=x.dtype, device=x.device)
164
+ pos_embed = position_grid_to_embed(pos_embed, x.shape[1])
165
+ pos_embed = pos_embed * ratio
166
+ pos_embed = pos_embed.permute(2, 0, 1)[None].expand(x.shape[0], -1, -1, -1)
167
+ return x + pos_embed
168
+
169
+ def scratch_forward(self, features: list[torch.Tensor]) -> torch.Tensor:
170
+ layer_1, layer_2, layer_3, layer_4 = features
171
+
172
+ layer_1_rn = self.scratch.layer1_rn(layer_1)
173
+ layer_2_rn = self.scratch.layer2_rn(layer_2)
174
+ layer_3_rn = self.scratch.layer3_rn(layer_3)
175
+ layer_4_rn = self.scratch.layer4_rn(layer_4)
176
+
177
+ out = self.scratch.refinenet4(layer_4_rn, size=layer_3_rn.shape[2:])
178
+ out = self.scratch.refinenet3(out, layer_3_rn, size=layer_2_rn.shape[2:])
179
+ out = self.scratch.refinenet2(out, layer_2_rn, size=layer_1_rn.shape[2:])
180
+ return self.scratch.refinenet1(out, layer_1_rn, size=layer_1_rn.shape[2:])
181
+
182
+
183
+ def _make_dense_resize_layer(channels: int, resize_scale: float) -> nn.Module:
184
+ if resize_scale == 1.0:
185
+ return nn.Identity()
186
+
187
+ if resize_scale == 0.5:
188
+ return nn.Conv2d(
189
+ in_channels=channels,
190
+ out_channels=channels,
191
+ kernel_size=3,
192
+ stride=2,
193
+ padding=1,
194
+ )
195
+
196
+ upsample_scale = int(resize_scale)
197
+ return nn.ConvTranspose2d(
198
+ in_channels=channels,
199
+ out_channels=channels,
200
+ kernel_size=upsample_scale,
201
+ stride=upsample_scale,
202
+ padding=0,
203
+ )
204
+
205
+
206
+ def _make_prediction_head(in_channels: int, out_channels: int) -> nn.Module:
207
+ return nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=True)
208
+
209
+
210
+ def _init_small_conf_prediction_head(proj: nn.Module) -> None:
211
+ if not isinstance(proj, nn.Conv2d):
212
+ raise TypeError(f"Unsupported confidence projection layer: {type(proj)}")
213
+
214
+ nn.init.zeros_(proj.weight)
215
+ if proj.bias is None:
216
+ raise ValueError("Small confidence init requires a bias term for proj_conf")
217
+
218
+ # With expp1 confidence activation this starts from conf ~= 1.05.
219
+ nn.init.constant_(proj.bias, math.log(1.05 - 1.0))
220
+
221
+
222
+ def _make_fusion_block(features: int, has_residual: bool = True) -> nn.Module:
223
+ return FeatureFusionBlock(
224
+ features,
225
+ nn.ReLU(inplace=False),
226
+ has_residual=has_residual,
227
+ )
228
+
229
+
230
+ def _make_scratch(in_shape: list[int], out_shape: int) -> nn.Module:
231
+ scratch = nn.Module()
232
+ scratch.layer1_rn = nn.Conv2d(in_shape[0], out_shape, kernel_size=3, stride=1, padding=1, bias=False)
233
+ scratch.layer2_rn = nn.Conv2d(in_shape[1], out_shape, kernel_size=3, stride=1, padding=1, bias=False)
234
+ scratch.layer3_rn = nn.Conv2d(in_shape[2], out_shape, kernel_size=3, stride=1, padding=1, bias=False)
235
+ scratch.layer4_rn = nn.Conv2d(in_shape[3], out_shape, kernel_size=3, stride=1, padding=1, bias=False)
236
+ return scratch
237
+
238
+
239
+ class ResidualConvUnit(nn.Module):
240
+ def __init__(self, features: int, activation: nn.Module) -> None:
241
+ super().__init__()
242
+ self.conv1 = nn.Conv2d(features, features, kernel_size=3, stride=1, padding=1, bias=True)
243
+ self.conv2 = nn.Conv2d(features, features, kernel_size=3, stride=1, padding=1, bias=True)
244
+ self.activation = activation
245
+
246
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
247
+ out = self.activation(x)
248
+ out = self.conv1(out)
249
+ out = self.activation(out)
250
+ out = self.conv2(out)
251
+ return out + x
252
+
253
+
254
+ class FeatureFusionBlock(nn.Module):
255
+ def __init__(self, features: int, activation: nn.Module, has_residual: bool = True) -> None:
256
+ super().__init__()
257
+ self.out_conv = nn.Conv2d(features, features, kernel_size=1, stride=1, padding=0, bias=True)
258
+ self.has_residual = has_residual
259
+ if has_residual:
260
+ self.resConfUnit1 = ResidualConvUnit(features, activation)
261
+ self.resConfUnit2 = ResidualConvUnit(features, activation)
262
+
263
+ def forward(self, x: torch.Tensor, residual: torch.Tensor | None = None, size: Tuple[int, int] | None = None) -> torch.Tensor:
264
+ output = x
265
+ if self.has_residual:
266
+ if residual is None:
267
+ raise ValueError("FeatureFusionBlock requires a residual tensor when has_residual=True")
268
+ output = output + self.resConfUnit1(residual)
269
+
270
+ output = self.resConfUnit2(output)
271
+ output = custom_interpolate(output, size=size, mode="bilinear", align_corners=True)
272
+ return self.out_conv(output)
273
+
274
+
275
+ def custom_interpolate(
276
+ x: torch.Tensor,
277
+ size: Tuple[int, int] | None = None,
278
+ scale_factor: float | None = None,
279
+ mode: str = "bilinear",
280
+ align_corners: bool = True,
281
+ ) -> torch.Tensor:
282
+ if size is None:
283
+ if scale_factor is None:
284
+ raise ValueError("custom_interpolate requires either size or scale_factor")
285
+ size = (
286
+ int(x.shape[-2] * scale_factor),
287
+ int(x.shape[-1] * scale_factor),
288
+ )
289
+
290
+ if tuple(x.shape[-2:]) == tuple(size):
291
+ return x
292
+
293
+ int_max = 1610612736
294
+ input_elements = size[0] * size[1] * x.shape[0] * x.shape[1]
295
+ if input_elements <= int_max:
296
+ return F.interpolate(x, size=size, mode=mode, align_corners=align_corners)
297
+
298
+ chunks = torch.chunk(x, chunks=(input_elements // int_max) + 1, dim=0)
299
+ interpolated_chunks = [
300
+ F.interpolate(
301
+ chunk,
302
+ size=size,
303
+ mode=mode,
304
+ align_corners=align_corners,
305
+ )
306
+ for chunk in chunks
307
+ ]
308
+ return torch.cat(interpolated_chunks, dim=0).contiguous()
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/text_alignment_head.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+
11
+ from vggt_omega.models.layers import SelfAttentionBlock
12
+
13
+
14
+ class TextAlignmentHead(nn.Module):
15
+ """Read out a language-aligned sequence embedding from camera/register tokens."""
16
+
17
+ def __init__(self, dim_in: int = 2048) -> None:
18
+ super().__init__()
19
+ self.token_norm = nn.LayerNorm(dim_in, eps=1e-5)
20
+
21
+ self.language_token = nn.Parameter(torch.zeros(1, 1, dim_in))
22
+ nn.init.trunc_normal_(self.language_token, std=0.02)
23
+
24
+ self.readout_blocks = nn.ModuleList(
25
+ [
26
+ SelfAttentionBlock(
27
+ dim=dim_in,
28
+ num_heads=16,
29
+ ffn_ratio=4.0,
30
+ qkv_bias=True,
31
+ proj_bias=True,
32
+ ffn_bias=True,
33
+ init_values=1e-5,
34
+ use_qk_norm=False,
35
+ mask_k_bias=True,
36
+ )
37
+ for _ in range(4)
38
+ ]
39
+ )
40
+ self.language_token_norm = nn.LayerNorm(dim_in, eps=1e-5)
41
+ self.embedding_projector = nn.Sequential(
42
+ nn.Linear(dim_in, dim_in // 2, bias=True),
43
+ nn.GELU(),
44
+ nn.LayerNorm(dim_in // 2, eps=1e-5),
45
+ nn.Linear(dim_in // 2, dim_in, bias=True),
46
+ )
47
+
48
+ def forward(
49
+ self,
50
+ aggregated_tokens_list: list[torch.Tensor | None],
51
+ patch_token_start: int,
52
+ ) -> dict[str, torch.Tensor]:
53
+ tokens = aggregated_tokens_list[-1]
54
+ if tokens is None:
55
+ raise ValueError("Aggregator did not cache the final layer, which TextAlignmentHead needs.")
56
+ if patch_token_start is None:
57
+ raise ValueError("patch_token_start is required for TextAlignmentHead")
58
+ if patch_token_start > tokens.shape[2]:
59
+ raise ValueError(f"patch_token_start ({patch_token_start}) exceeds token length ({tokens.shape[2]})")
60
+
61
+ if tokens.dtype != torch.float32:
62
+ tokens = tokens.float()
63
+
64
+ batch_size, num_frames, _, _ = tokens.shape
65
+ camera_and_register_tokens = tokens[:, :, :patch_token_start]
66
+ camera_and_register_tokens = self.token_norm(camera_and_register_tokens)
67
+ camera_and_register_tokens = camera_and_register_tokens.reshape(batch_size, num_frames * patch_token_start, -1)
68
+
69
+ language_token = self.language_token.expand(batch_size, -1, -1)
70
+ readout_tokens = torch.cat([language_token, camera_and_register_tokens], dim=1)
71
+ for block in self.readout_blocks:
72
+ readout_tokens = block(readout_tokens, None)
73
+
74
+ language_token = self.language_token_norm(readout_tokens[:, 0])
75
+ text_alignment_embedding = self.embedding_projector(language_token)
76
+ return {
77
+ "text_alignment_embedding": F.normalize(text_alignment_embedding, dim=-1),
78
+ "text_alignment_token": language_token,
79
+ }
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/utils.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ import torch
8
+
9
+
10
+ def position_grid_to_embed(pos_grid: torch.Tensor, embed_dim: int, omega_0: float = 100) -> torch.Tensor:
11
+ """
12
+ Convert 2D position grid (HxWx2) to sinusoidal embeddings (HxWxC)
13
+
14
+ Args:
15
+ pos_grid: Tensor of shape (H, W, 2) containing 2D coordinates
16
+ embed_dim: Output channel dimension for embeddings
17
+
18
+ Returns:
19
+ Tensor of shape (H, W, embed_dim) with positional embeddings
20
+ """
21
+ H, W, grid_dim = pos_grid.shape
22
+ assert grid_dim == 2
23
+ pos_flat = pos_grid.reshape(-1, grid_dim) # Flatten to (H*W, 2)
24
+
25
+ # Process x and y coordinates separately
26
+ emb_x = make_sincos_pos_embed(embed_dim // 2, pos_flat[:, 0], omega_0=omega_0) # [1, H*W, D/2]
27
+ emb_y = make_sincos_pos_embed(embed_dim // 2, pos_flat[:, 1], omega_0=omega_0) # [1, H*W, D/2]
28
+
29
+ # Combine and reshape
30
+ emb = torch.cat([emb_x, emb_y], dim=-1) # [1, H*W, D]
31
+
32
+ return emb.view(H, W, embed_dim) # [H, W, D]
33
+
34
+
35
+ def make_sincos_pos_embed(embed_dim: int, pos: torch.Tensor, omega_0: float = 100) -> torch.Tensor:
36
+ """
37
+ This function generates a 1D positional embedding from a given grid using sine and cosine functions.
38
+
39
+ Args:
40
+ - embed_dim: The embedding dimension.
41
+ - pos: The position to generate the embedding from.
42
+
43
+ Returns:
44
+ - emb: The generated 1D positional embedding.
45
+ """
46
+ assert embed_dim % 2 == 0
47
+ device = pos.device
48
+ omega = torch.arange(embed_dim // 2, dtype=torch.float32 if device.type == "mps" else torch.double, device=device)
49
+ omega /= embed_dim / 2.0
50
+ omega = 1.0 / omega_0**omega # (D/2,)
51
+
52
+ pos = pos.reshape(-1) # (M,)
53
+ out = torch.einsum("m,d->md", pos, omega) # (M, D/2), outer product
54
+
55
+ emb_sin = torch.sin(out) # (M, D/2)
56
+ emb_cos = torch.cos(out) # (M, D/2)
57
+
58
+ emb = torch.cat([emb_sin, emb_cos], dim=1) # (M, D)
59
+ return emb.float()
60
+
61
+
62
+ # Inspired by https://github.com/microsoft/moge
63
+
64
+
65
+ def create_uv_grid(
66
+ width: int, height: int, aspect_ratio: float = None, dtype: torch.dtype = None, device: torch.device = None
67
+ ) -> torch.Tensor:
68
+ """
69
+ Create a normalized UV grid of shape (width, height, 2).
70
+
71
+ The grid spans horizontally and vertically according to an aspect ratio,
72
+ ensuring the top-left corner is at (-x_span, -y_span) and the bottom-right
73
+ corner is at (x_span, y_span), normalized by the diagonal of the plane.
74
+
75
+ Args:
76
+ width (int): Number of points horizontally.
77
+ height (int): Number of points vertically.
78
+ aspect_ratio (float, optional): Width-to-height ratio. Defaults to width/height.
79
+ dtype (torch.dtype, optional): Data type of the resulting tensor.
80
+ device (torch.device, optional): Device on which the tensor is created.
81
+
82
+ Returns:
83
+ torch.Tensor: A (width, height, 2) tensor of UV coordinates.
84
+ """
85
+ # Derive aspect ratio if not explicitly provided
86
+ if aspect_ratio is None:
87
+ aspect_ratio = float(width) / float(height)
88
+
89
+ # Compute normalized spans for X and Y
90
+ diag_factor = (aspect_ratio**2 + 1.0) ** 0.5
91
+ span_x = aspect_ratio / diag_factor
92
+ span_y = 1.0 / diag_factor
93
+
94
+ # Establish the linspace boundaries
95
+ left_x = -span_x * (width - 1) / width
96
+ right_x = span_x * (width - 1) / width
97
+ top_y = -span_y * (height - 1) / height
98
+ bottom_y = span_y * (height - 1) / height
99
+
100
+ # Generate 1D coordinates
101
+ x_coords = torch.linspace(left_x, right_x, steps=width, dtype=dtype, device=device)
102
+ y_coords = torch.linspace(top_y, bottom_y, steps=height, dtype=dtype, device=device)
103
+
104
+ # Create 2D meshgrid (width x height) and stack into UV
105
+ uu, vv = torch.meshgrid(x_coords, y_coords, indexing="xy")
106
+ uv_grid = torch.stack((uu, vv), dim=-1)
107
+
108
+ return uv_grid
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/__init__.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ from .attention import CausalSelfAttention, LinearKMaskedBias, SelfAttention
8
+ from .block import CausalSelfAttentionBlock, SelfAttentionBlock
9
+ from .ffn_layers import Mlp, SwiGLUFFN
10
+ from .layer_scale import LayerScale
11
+ from .patch_embed import PatchEmbed
12
+ from .rms_norm import RMSNorm
13
+ from .rope_position_encoding import RopePositionEmbedding
14
+
15
+ __all__ = [
16
+ "CausalSelfAttention",
17
+ "CausalSelfAttentionBlock",
18
+ "LayerScale",
19
+ "LinearKMaskedBias",
20
+ "Mlp",
21
+ "PatchEmbed",
22
+ "RMSNorm",
23
+ "RopePositionEmbedding",
24
+ "SelfAttention",
25
+ "SelfAttentionBlock",
26
+ "SwiGLUFFN",
27
+ ]
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/attention.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
8
+ #
9
+ # This software may be used and distributed in accordance with
10
+ # the terms of the DINOv3 License Agreement.
11
+
12
+ import math
13
+ from typing import List, Tuple
14
+
15
+ from torch import Tensor, nn
16
+ import torch
17
+ import torch.nn.functional as F
18
+
19
+ from .utils import cat_keep_shapes, uncat_with_shapes
20
+
21
+
22
+ # RoPE-related functions:
23
+ def rope_rotate_half(x: Tensor) -> Tensor:
24
+ # x: [ x0 x1 x2 x3 x4 x5]
25
+ # out: [-x3 -x4 -x5 x0 x1 x2]
26
+ x1, x2 = x.chunk(2, dim=-1)
27
+ return torch.cat([-x2, x1], dim=-1)
28
+
29
+
30
+ def rope_apply(x: Tensor, sin: Tensor, cos: Tensor) -> Tensor:
31
+ # x: [..., D], eg [x0, x1, x2, x3, x4, x5]
32
+ # sin: [..., D], eg [sin0, sin1, sin2, sin0, sin1, sin2]
33
+ # cos: [..., D], eg [cos0, cos1, cos2, cos0, cos1, cos2]
34
+ return (x * cos) + (rope_rotate_half(x) * sin)
35
+
36
+
37
+ class LinearKMaskedBias(nn.Linear):
38
+ def __init__(self, *args, **kwargs):
39
+ super().__init__(*args, **kwargs)
40
+ o = self.out_features
41
+ assert o % 3 == 0
42
+ if self.bias is not None:
43
+ self.register_buffer("bias_mask", torch.full_like(self.bias, fill_value=math.nan))
44
+
45
+ def forward(self, input: Tensor) -> Tensor:
46
+ masked_bias = self.bias * self.bias_mask.to(self.bias.dtype) if self.bias is not None else None
47
+ return F.linear(input, self.weight, masked_bias)
48
+
49
+
50
+ class SelfAttention(nn.Module):
51
+ def __init__(
52
+ self,
53
+ dim: int,
54
+ num_heads: int = 8,
55
+ qkv_bias: bool = False,
56
+ proj_bias: bool = True,
57
+ attn_drop: float = 0.0,
58
+ proj_drop: float = 0.0,
59
+ mask_k_bias: bool = False,
60
+ use_qk_norm: bool = False,
61
+ device=None,
62
+ ) -> None:
63
+ super().__init__()
64
+ self.num_heads = num_heads
65
+ head_dim = dim // num_heads
66
+ self.scale = head_dim**-0.5
67
+ # VGGT-Omega change: the aggregator checkpoint was trained with Q/K
68
+ # normalization, while upstream DINOv3 attention does not expose it.
69
+ self.use_qk_norm = use_qk_norm
70
+ if self.use_qk_norm:
71
+ self.q_norm = nn.LayerNorm(head_dim, eps=1e-5)
72
+ self.k_norm = nn.LayerNorm(head_dim, eps=1e-5)
73
+ else:
74
+ self.q_norm = None
75
+ self.k_norm = None
76
+
77
+ linear_class = LinearKMaskedBias if mask_k_bias else nn.Linear
78
+ self.qkv = linear_class(dim, dim * 3, bias=qkv_bias, device=device)
79
+ self.attn_drop = nn.Dropout(attn_drop)
80
+ self.proj = nn.Linear(dim, dim, bias=proj_bias, device=device)
81
+ self.proj_drop = nn.Dropout(proj_drop)
82
+
83
+ def apply_rope(self, q: Tensor, k: Tensor, rope: Tensor | Tuple[Tensor, Tensor]) -> Tuple[Tensor, Tensor]:
84
+ # All operations will use the dtype of rope, the output is cast back to the dtype of q and k
85
+ q_dtype = q.dtype
86
+ k_dtype = k.dtype
87
+ sin, cos = rope
88
+ rope_dtype = sin.dtype
89
+ q = q.to(dtype=rope_dtype)
90
+ k = k.to(dtype=rope_dtype)
91
+ N = q.shape[-2]
92
+ prefix = N - sin.shape[-2]
93
+ assert prefix >= 0
94
+ q_prefix = q[:, :, :prefix, :]
95
+ q = rope_apply(q[:, :, prefix:, :], sin, cos) # [B, head, hw, D//head]
96
+ q = torch.cat((q_prefix, q), dim=-2) # [B, head, N, D//head]
97
+ k_prefix = k[:, :, :prefix, :]
98
+ k = rope_apply(k[:, :, prefix:, :], sin, cos) # [B, head, hw, D//head]
99
+ k = torch.cat((k_prefix, k), dim=-2) # [B, head, N, D//head]
100
+ q = q.to(dtype=q_dtype)
101
+ k = k.to(dtype=k_dtype)
102
+ return q, k
103
+
104
+ def forward(self, x: Tensor, attn_bias=None, rope: Tensor = None) -> Tensor:
105
+ qkv = self.qkv(x)
106
+ attn_v = self.compute_attention(qkv=qkv, attn_bias=attn_bias, rope=rope)
107
+ x = self.proj(attn_v)
108
+ x = self.proj_drop(x)
109
+ return x
110
+
111
+ def forward_list(self, x_list, attn_bias=None, rope_list=None) -> List[Tensor]:
112
+ assert len(x_list) == len(rope_list) # should be enforced by the Block
113
+ x_flat, shapes, num_tokens = cat_keep_shapes(x_list)
114
+ qkv_flat = self.qkv(x_flat)
115
+ qkv_list = uncat_with_shapes(qkv_flat, shapes, num_tokens)
116
+ att_out = []
117
+ for _, (qkv, _, rope) in enumerate(zip(qkv_list, shapes, rope_list)):
118
+ att_out.append(self.compute_attention(qkv, attn_bias=attn_bias, rope=rope))
119
+ x_flat, shapes, num_tokens = cat_keep_shapes(att_out)
120
+ x_flat = self.proj(x_flat)
121
+ return uncat_with_shapes(x_flat, shapes, num_tokens)
122
+
123
+ def compute_attention(self, qkv: Tensor, attn_bias=None, rope=None) -> Tensor:
124
+ assert attn_bias is None
125
+ B, N, _ = qkv.shape
126
+ C = self.qkv.in_features
127
+
128
+ qkv = qkv.reshape(B, N, 3, self.num_heads, C // self.num_heads)
129
+ q, k, v = torch.unbind(qkv, 2)
130
+ q, k, v = [t.transpose(1, 2) for t in [q, k, v]]
131
+ if self.use_qk_norm:
132
+ q = self.q_norm(q)
133
+ k = self.k_norm(k)
134
+ if rope is not None:
135
+ q, k = self.apply_rope(q, k, rope)
136
+ x = torch.nn.functional.scaled_dot_product_attention(q, k, v)
137
+ x = x.transpose(1, 2)
138
+ return x.reshape([B, N, C])
139
+
140
+
141
+ class CausalSelfAttention(nn.Module):
142
+ def __init__(
143
+ self,
144
+ dim: int,
145
+ num_heads: int = 8,
146
+ qkv_bias: bool = False,
147
+ proj_bias: bool = True,
148
+ attn_drop: float = 0.0,
149
+ proj_drop: float = 0.0,
150
+ ) -> None:
151
+ super().__init__()
152
+ self.dim = dim
153
+ self.num_heads = num_heads
154
+ head_dim = dim // num_heads
155
+ self.scale = head_dim**-0.5
156
+
157
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
158
+ self.attn_drop = attn_drop
159
+ self.proj = nn.Linear(dim, dim, bias=proj_bias)
160
+ self.proj_drop = nn.Dropout(proj_drop)
161
+
162
+ def init_weights(
163
+ self, init_attn_std: float | None = None, init_proj_std: float | None = None, factor: float = 1.0
164
+ ) -> None:
165
+ init_attn_std = init_attn_std or (self.dim**-0.5)
166
+ init_proj_std = init_proj_std or init_attn_std * factor
167
+ nn.init.normal_(self.qkv.weight, std=init_attn_std)
168
+ nn.init.normal_(self.proj.weight, std=init_proj_std)
169
+ if self.qkv.bias is not None:
170
+ nn.init.zeros_(self.qkv.bias)
171
+ if self.proj.bias is not None:
172
+ nn.init.zeros_(self.proj.bias)
173
+
174
+ def forward(self, x: Tensor, is_causal: bool = True) -> Tensor:
175
+ B, N, C = x.shape
176
+ qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads)
177
+ q, k, v = torch.unbind(qkv, 2)
178
+ q, k, v = [t.transpose(1, 2) for t in [q, k, v]]
179
+ x = torch.nn.functional.scaled_dot_product_attention(
180
+ q, k, v, attn_mask=None, dropout_p=self.attn_drop if self.training else 0, is_causal=is_causal
181
+ )
182
+ x = x.transpose(1, 2).contiguous().view(B, N, C)
183
+ x = self.proj_drop(self.proj(x))
184
+ return x
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/block.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
8
+ #
9
+ # This software may be used and distributed in accordance with
10
+ # the terms of the DINOv3 License Agreement.
11
+
12
+ from typing import Callable, List, Optional
13
+
14
+ import torch
15
+ from torch import Tensor, nn
16
+
17
+ from .attention import CausalSelfAttention, SelfAttention
18
+ from .ffn_layers import Mlp
19
+ from .layer_scale import LayerScale # , DropPath
20
+ from .utils import cat_keep_shapes, uncat_with_shapes
21
+
22
+ class SelfAttentionBlock(nn.Module):
23
+ def __init__(
24
+ self,
25
+ dim: int,
26
+ num_heads: int,
27
+ ffn_ratio: float = 4.0,
28
+ qkv_bias: bool = False,
29
+ proj_bias: bool = True,
30
+ ffn_bias: bool = True,
31
+ drop: float = 0.0,
32
+ attn_drop: float = 0.0,
33
+ init_values=None,
34
+ drop_path: float = 0.0,
35
+ act_layer: Callable[..., nn.Module] = nn.GELU,
36
+ norm_layer: Callable[..., nn.Module] = nn.LayerNorm,
37
+ attn_class: Callable[..., nn.Module] = SelfAttention,
38
+ ffn_layer: Callable[..., nn.Module] = Mlp,
39
+ mask_k_bias: bool = False,
40
+ use_qk_norm: bool = False,
41
+ device=None,
42
+ ) -> None:
43
+ super().__init__()
44
+ # print(f"biases: qkv: {qkv_bias}, proj: {proj_bias}, ffn: {ffn_bias}")
45
+ self.norm1 = norm_layer(dim)
46
+ self.attn = attn_class(
47
+ dim,
48
+ num_heads=num_heads,
49
+ qkv_bias=qkv_bias,
50
+ proj_bias=proj_bias,
51
+ attn_drop=attn_drop,
52
+ proj_drop=drop,
53
+ mask_k_bias=mask_k_bias,
54
+ # VGGT-Omega change: pass through Q/K normalization for the
55
+ # aggregator blocks trained with q_norm/k_norm parameters.
56
+ use_qk_norm=use_qk_norm,
57
+ device=device,
58
+ )
59
+ self.ls1 = LayerScale(dim, init_values=init_values, device=device) if init_values else nn.Identity()
60
+
61
+ self.norm2 = norm_layer(dim)
62
+ mlp_hidden_dim = int(dim * ffn_ratio)
63
+ self.mlp = ffn_layer(
64
+ in_features=dim,
65
+ hidden_features=mlp_hidden_dim,
66
+ act_layer=act_layer,
67
+ drop=drop,
68
+ bias=ffn_bias,
69
+ device=device,
70
+ )
71
+ self.ls2 = LayerScale(dim, init_values=init_values, device=device) if init_values else nn.Identity()
72
+
73
+ self.sample_drop_ratio = drop_path
74
+
75
+ @staticmethod
76
+ def _maybe_index_rope(rope: tuple[Tensor, Tensor] | None, indices: Tensor) -> tuple[Tensor, Tensor] | None:
77
+ if rope is None:
78
+ return None
79
+
80
+ sin, cos = rope
81
+ assert sin.ndim == cos.ndim
82
+ if sin.ndim == 4:
83
+ # If the rope embedding has a batch dimension (is different for each batch element), index into it
84
+ return sin[indices], cos[indices] # [batch, heads, patches, embed_dim]
85
+ else:
86
+ # No batch dimension, do not index
87
+ return sin, cos # [heads, patches, embed_dim] or [patches, embed_dim]
88
+
89
+ def _forward(self, x: Tensor, rope=None) -> Tensor:
90
+ """
91
+ This is the reference implementation for a single tensor, matching what is done below for a list.
92
+ We call the list op on [x] instead of this function.
93
+ """
94
+ b, _, _ = x.shape
95
+ sample_subset_size = max(int(b * (1 - self.sample_drop_ratio)), 1)
96
+ residual_scale_factor = b / sample_subset_size
97
+
98
+ if self.training and self.sample_drop_ratio > 0.0:
99
+ indices_1 = (torch.randperm(b, device=x.device))[:sample_subset_size]
100
+
101
+ x_subset_1 = x[indices_1]
102
+ rope_subset = self._maybe_index_rope(rope, indices_1)
103
+ residual_1 = self.attn(self.norm1(x_subset_1), rope=rope_subset)
104
+
105
+ x_attn = torch.index_add(
106
+ x,
107
+ dim=0,
108
+ source=self.ls1(residual_1),
109
+ index=indices_1,
110
+ alpha=residual_scale_factor,
111
+ )
112
+
113
+ indices_2 = (torch.randperm(b, device=x.device))[:sample_subset_size]
114
+
115
+ x_subset_2 = x_attn[indices_2]
116
+ residual_2 = self.mlp(self.norm2(x_subset_2))
117
+
118
+ x_ffn = torch.index_add(
119
+ x_attn,
120
+ dim=0,
121
+ source=self.ls2(residual_2),
122
+ index=indices_2,
123
+ alpha=residual_scale_factor,
124
+ )
125
+ else:
126
+ x_attn = x + self.ls1(self.attn(self.norm1(x), rope=rope))
127
+ x_ffn = x_attn + self.ls2(self.mlp(self.norm2(x_attn)))
128
+
129
+ return x_ffn
130
+
131
+ def _forward_list(self, x_list: List[Tensor], rope_list=None) -> List[Tensor]:
132
+ """
133
+ This list operator concatenates the tokens from the list of inputs together to save
134
+ on the elementwise operations. Torch-compile memory-planning allows hiding the overhead
135
+ related to concat ops.
136
+ """
137
+ b_list = [x.shape[0] for x in x_list]
138
+ sample_subset_sizes = [max(int(b * (1 - self.sample_drop_ratio)), 1) for b in b_list]
139
+ residual_scale_factors = [b / sample_subset_size for b, sample_subset_size in zip(b_list, sample_subset_sizes)]
140
+
141
+ if self.training and self.sample_drop_ratio > 0.0:
142
+ indices_1_list = [
143
+ (torch.randperm(b, device=x.device))[:sample_subset_size]
144
+ for x, b, sample_subset_size in zip(x_list, b_list, sample_subset_sizes)
145
+ ]
146
+ x_subset_1_list = [x[indices_1] for x, indices_1 in zip(x_list, indices_1_list)]
147
+
148
+ if rope_list is not None:
149
+ rope_subset_list = [
150
+ self._maybe_index_rope(rope, indices_1) for rope, indices_1 in zip(rope_list, indices_1_list)
151
+ ]
152
+ else:
153
+ rope_subset_list = rope_list
154
+
155
+ flattened, shapes, num_tokens = cat_keep_shapes(x_subset_1_list)
156
+ norm1 = uncat_with_shapes(self.norm1(flattened), shapes, num_tokens)
157
+ residual_1_list = self.attn.forward_list(norm1, rope_list=rope_subset_list)
158
+
159
+ x_attn_list = [
160
+ torch.index_add(
161
+ x,
162
+ dim=0,
163
+ source=self.ls1(residual_1),
164
+ index=indices_1,
165
+ alpha=residual_scale_factor,
166
+ )
167
+ for x, residual_1, indices_1, residual_scale_factor in zip(
168
+ x_list, residual_1_list, indices_1_list, residual_scale_factors
169
+ )
170
+ ]
171
+
172
+ indices_2_list = [
173
+ (torch.randperm(b, device=x.device))[:sample_subset_size]
174
+ for x, b, sample_subset_size in zip(x_list, b_list, sample_subset_sizes)
175
+ ]
176
+ x_subset_2_list = [x[indices_2] for x, indices_2 in zip(x_attn_list, indices_2_list)]
177
+ flattened, shapes, num_tokens = cat_keep_shapes(x_subset_2_list)
178
+ norm2_flat = self.norm2(flattened)
179
+ norm2_list = uncat_with_shapes(norm2_flat, shapes, num_tokens)
180
+
181
+ residual_2_list = self.mlp.forward_list(norm2_list)
182
+
183
+ x_ffn = [
184
+ torch.index_add(
185
+ x_attn,
186
+ dim=0,
187
+ source=self.ls2(residual_2),
188
+ index=indices_2,
189
+ alpha=residual_scale_factor,
190
+ )
191
+ for x_attn, residual_2, indices_2, residual_scale_factor in zip(
192
+ x_attn_list, residual_2_list, indices_2_list, residual_scale_factors
193
+ )
194
+ ]
195
+ else:
196
+ x_out = []
197
+ for x, rope in zip(x_list, rope_list):
198
+ x_attn = x + self.ls1(self.attn(self.norm1(x), rope=rope))
199
+ x_ffn = x_attn + self.ls2(self.mlp(self.norm2(x_attn)))
200
+ x_out.append(x_ffn)
201
+ x_ffn = x_out
202
+
203
+ return x_ffn
204
+
205
+ def forward(self, x_or_x_list, rope_or_rope_list=None) -> List[Tensor]:
206
+ if isinstance(x_or_x_list, Tensor):
207
+ # for reference:
208
+ # return self._forward(x_or_x_list, rope=rope_or_rope_list)
209
+ # in order to match implementations we call the list op:
210
+ return self._forward_list([x_or_x_list], rope_list=[rope_or_rope_list])[0]
211
+ elif isinstance(x_or_x_list, list):
212
+ if rope_or_rope_list is None:
213
+ rope_or_rope_list = [None for x in x_or_x_list]
214
+ # return [self._forward(x, rope=rope) for x, rope in zip(x_or_x_list, rope_or_rope_list)]
215
+ return self._forward_list(x_or_x_list, rope_list=rope_or_rope_list)
216
+ else:
217
+ raise AssertionError
218
+
219
+
220
+ class CausalSelfAttentionBlock(nn.Module):
221
+ def __init__(
222
+ self,
223
+ dim: int,
224
+ num_heads: int,
225
+ ffn_ratio: float = 4.0,
226
+ ls_init_value: Optional[float] = None,
227
+ is_causal: bool = True,
228
+ act_layer: Callable = nn.GELU,
229
+ norm_layer: Callable = nn.LayerNorm,
230
+ dropout_prob: float = 0.0,
231
+ ):
232
+ super().__init__()
233
+
234
+ self.dim = dim
235
+ self.is_causal = is_causal
236
+ self.ls1 = LayerScale(dim, init_values=ls_init_value) if ls_init_value else nn.Identity()
237
+ self.attention_norm = norm_layer(dim)
238
+ self.attention = CausalSelfAttention(dim, num_heads, attn_drop=dropout_prob, proj_drop=dropout_prob)
239
+
240
+ self.ffn_norm = norm_layer(dim)
241
+ ffn_hidden_dim = int(dim * ffn_ratio)
242
+ self.feed_forward = Mlp(
243
+ in_features=dim,
244
+ hidden_features=ffn_hidden_dim,
245
+ drop=dropout_prob,
246
+ act_layer=act_layer,
247
+ )
248
+
249
+ self.ls2 = LayerScale(dim, init_values=ls_init_value) if ls_init_value else nn.Identity()
250
+
251
+ def init_weights(
252
+ self,
253
+ init_attn_std: float | None = None,
254
+ init_proj_std: float | None = None,
255
+ init_fc_std: float | None = None,
256
+ factor: float = 1.0,
257
+ ) -> None:
258
+ init_attn_std = init_attn_std or (self.dim**-0.5)
259
+ init_proj_std = init_proj_std or init_attn_std * factor
260
+ init_fc_std = init_fc_std or (2 * self.dim) ** -0.5
261
+ self.attention.init_weights(init_attn_std, init_proj_std)
262
+ self.attention_norm.reset_parameters()
263
+ nn.init.normal_(self.feed_forward.fc1.weight, std=init_fc_std)
264
+ nn.init.normal_(self.feed_forward.fc2.weight, std=init_proj_std)
265
+ self.ffn_norm.reset_parameters()
266
+
267
+ def forward(
268
+ self,
269
+ x: torch.Tensor,
270
+ ):
271
+
272
+ x_attn = x + self.ls1(self.attention(self.attention_norm(x), self.is_causal))
273
+ x_ffn = x_attn + self.ls2(self.feed_forward(self.ffn_norm(x_attn)))
274
+ return x_ffn
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/ffn_layers.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
8
+ #
9
+ # This software may be used and distributed in accordance with
10
+ # the terms of the DINOv3 License Agreement.
11
+
12
+ from typing import Callable, List, Optional
13
+
14
+ import torch.nn.functional as F
15
+ from torch import Tensor, nn
16
+
17
+ from .utils import cat_keep_shapes, uncat_with_shapes
18
+
19
+
20
+ class ListForwardMixin(object):
21
+ def forward(self, x: Tensor):
22
+ raise NotImplementedError
23
+
24
+ def forward_list(self, x_list: List[Tensor]) -> List[Tensor]:
25
+ x_flat, shapes, num_tokens = cat_keep_shapes(x_list)
26
+ x_flat = self.forward(x_flat)
27
+ return uncat_with_shapes(x_flat, shapes, num_tokens)
28
+
29
+
30
+ class Mlp(nn.Module, ListForwardMixin):
31
+ def __init__(
32
+ self,
33
+ in_features: int,
34
+ hidden_features: Optional[int] = None,
35
+ out_features: Optional[int] = None,
36
+ act_layer: Callable[..., nn.Module] = nn.GELU,
37
+ drop: float = 0.0,
38
+ bias: bool = True,
39
+ device=None,
40
+ ) -> None:
41
+ super().__init__()
42
+ out_features = out_features or in_features
43
+ hidden_features = hidden_features or in_features
44
+ self.fc1 = nn.Linear(in_features, hidden_features, bias=bias, device=device)
45
+ self.act = act_layer()
46
+ self.fc2 = nn.Linear(hidden_features, out_features, bias=bias, device=device)
47
+ self.drop = nn.Dropout(drop)
48
+
49
+ def forward(self, x: Tensor) -> Tensor:
50
+ x = self.fc1(x)
51
+ x = self.act(x)
52
+ x = self.drop(x)
53
+ x = self.fc2(x)
54
+ x = self.drop(x)
55
+ return x
56
+
57
+
58
+ class SwiGLUFFN(nn.Module, ListForwardMixin):
59
+ def __init__(
60
+ self,
61
+ in_features: int,
62
+ hidden_features: Optional[int] = None,
63
+ out_features: Optional[int] = None,
64
+ act_layer: Optional[Callable[..., nn.Module]] = None,
65
+ drop: float = 0.0,
66
+ bias: bool = True,
67
+ align_to: int = 8,
68
+ device=None,
69
+ ) -> None:
70
+ super().__init__()
71
+ out_features = out_features or in_features
72
+ hidden_features = hidden_features or in_features
73
+ d = int(hidden_features * 2 / 3)
74
+ swiglu_hidden_features = d + (-d % align_to)
75
+ self.w1 = nn.Linear(in_features, swiglu_hidden_features, bias=bias, device=device)
76
+ self.w2 = nn.Linear(in_features, swiglu_hidden_features, bias=bias, device=device)
77
+ self.w3 = nn.Linear(swiglu_hidden_features, out_features, bias=bias, device=device)
78
+
79
+ def forward(self, x: Tensor) -> Tensor:
80
+ x1 = self.w1(x)
81
+ x2 = self.w2(x)
82
+ hidden = F.silu(x1) * x2
83
+ return self.w3(hidden)
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/layer_scale.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
8
+ #
9
+ # This software may be used and distributed in accordance with
10
+ # the terms of the DINOv3 License Agreement.
11
+
12
+ from typing import Union
13
+
14
+ import torch
15
+ from torch import Tensor, nn
16
+
17
+
18
+ class LayerScale(nn.Module):
19
+ def __init__(
20
+ self,
21
+ dim: int,
22
+ init_values: Union[float, Tensor] = 1e-5,
23
+ inplace: bool = False,
24
+ device=None,
25
+ ) -> None:
26
+ super().__init__()
27
+ self.inplace = inplace
28
+ self.gamma = nn.Parameter(torch.empty(dim, device=device))
29
+ self.init_values = init_values
30
+
31
+ def reset_parameters(self):
32
+ nn.init.constant_(self.gamma, self.init_values)
33
+
34
+ def forward(self, x: Tensor) -> Tensor:
35
+ return x.mul_(self.gamma) if self.inplace else x * self.gamma
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/patch_embed.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
8
+ #
9
+ # This software may be used and distributed in accordance with
10
+ # the terms of the DINOv3 License Agreement.
11
+
12
+ import math
13
+ from typing import Callable, Tuple, Union
14
+
15
+ from torch import Tensor, nn
16
+
17
+
18
+ def make_2tuple(x):
19
+ if isinstance(x, tuple):
20
+ assert len(x) == 2
21
+ return x
22
+
23
+ assert isinstance(x, int)
24
+ return (x, x)
25
+
26
+
27
+ class PatchEmbed(nn.Module):
28
+ """
29
+ 2D image to patch embedding: (B,C,H,W) -> (B,N,D)
30
+
31
+ Args:
32
+ img_size: Image size.
33
+ patch_size: Patch token size.
34
+ in_chans: Number of input image channels.
35
+ embed_dim: Number of linear projection output channels.
36
+ norm_layer: Normalization layer.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ img_size: Union[int, Tuple[int, int]] = 224,
42
+ patch_size: Union[int, Tuple[int, int]] = 16,
43
+ in_chans: int = 3,
44
+ embed_dim: int = 768,
45
+ norm_layer: Callable | None = None,
46
+ flatten_embedding: bool = True,
47
+ ) -> None:
48
+ super().__init__()
49
+
50
+ image_HW = make_2tuple(img_size)
51
+ patch_HW = make_2tuple(patch_size)
52
+ patch_grid_size = (
53
+ image_HW[0] // patch_HW[0],
54
+ image_HW[1] // patch_HW[1],
55
+ )
56
+
57
+ self.img_size = image_HW
58
+ self.patch_size = patch_HW
59
+ self.patches_resolution = patch_grid_size
60
+ self.num_patches = patch_grid_size[0] * patch_grid_size[1]
61
+
62
+ self.in_chans = in_chans
63
+ self.embed_dim = embed_dim
64
+
65
+ self.flatten_embedding = flatten_embedding
66
+
67
+ self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_HW, stride=patch_HW)
68
+ self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()
69
+
70
+ def forward(self, x: Tensor) -> Tensor:
71
+ _, _, H, W = x.shape
72
+ # patch_H, patch_W = self.patch_size
73
+ # assert H % patch_H == 0, f"Input image height {H} is not a multiple of patch height {patch_H}"
74
+ # assert W % patch_W == 0, f"Input image width {W} is not a multiple of patch width: {patch_W}"
75
+
76
+ x = self.proj(x) # B C H W
77
+ H, W = x.size(2), x.size(3)
78
+ x = x.flatten(2).transpose(1, 2) # B HW C
79
+ x = self.norm(x)
80
+ if not self.flatten_embedding:
81
+ x = x.reshape(-1, H, W, self.embed_dim) # B H W C
82
+ return x
83
+
84
+ def flops(self) -> float:
85
+ Ho, Wo = self.patches_resolution
86
+ flops = Ho * Wo * self.embed_dim * self.in_chans * (self.patch_size[0] * self.patch_size[1])
87
+ if self.norm is not None:
88
+ flops += Ho * Wo * self.embed_dim
89
+ return flops
90
+
91
+ def reset_parameters(self):
92
+ k = 1 / (self.in_chans * (self.patch_size[0] ** 2))
93
+ nn.init.uniform_(self.proj.weight, -math.sqrt(k), math.sqrt(k))
94
+ if self.proj.bias is not None:
95
+ nn.init.uniform_(self.proj.bias, -math.sqrt(k), math.sqrt(k))
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/rms_norm.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
8
+ #
9
+ # This software may be used and distributed in accordance with
10
+ # the terms of the DINOv3 License Agreement.
11
+
12
+ import torch
13
+ from torch import Tensor, nn
14
+
15
+
16
+ class RMSNorm(nn.Module):
17
+ def __init__(self, dim: int, eps: float = 1e-5):
18
+ super().__init__()
19
+ self.weight = nn.Parameter(torch.ones(dim))
20
+ self.eps = eps
21
+
22
+ def reset_parameters(self) -> None:
23
+ nn.init.constant_(self.weight, 1)
24
+
25
+ def _norm(self, x: Tensor) -> Tensor:
26
+ return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
27
+
28
+ def forward(self, x: Tensor) -> Tensor:
29
+ output = self._norm(x.float()).type_as(x)
30
+ return output * self.weight
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/rope_position_encoding.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
8
+ #
9
+ # This software may be used and distributed in accordance with
10
+ # the terms of the DINOv3 License Agreement.
11
+
12
+ import math
13
+ from typing import Literal
14
+
15
+ import numpy as np
16
+ import torch
17
+ from torch import Tensor, nn
18
+
19
+
20
+ # RoPE positional embedding with no mixing of coordinates (axial) and no learnable weights
21
+ # Supports two parametrizations of the rope parameters: either using `base` or `min_period` and `max_period`.
22
+ class RopePositionEmbedding(nn.Module):
23
+ def __init__(
24
+ self,
25
+ embed_dim: int,
26
+ *,
27
+ num_heads: int,
28
+ base: float | None = 100.0,
29
+ min_period: float | None = None,
30
+ max_period: float | None = None,
31
+ normalize_coords: Literal["min", "max", "separate"] = "separate",
32
+ shift_coords: float | None = None,
33
+ jitter_coords: float | None = None,
34
+ rescale_coords: float | None = None,
35
+ dtype: torch.dtype | None = None,
36
+ device: torch.device | None = None,
37
+ ):
38
+ super().__init__()
39
+ assert embed_dim % (4 * num_heads) == 0
40
+ both_periods = min_period is not None and max_period is not None
41
+ if (base is None and not both_periods) or (base is not None and both_periods):
42
+ raise ValueError("Either `base` or `min_period`+`max_period` must be provided.")
43
+
44
+ D_head = embed_dim // num_heads
45
+ self.base = base
46
+ self.min_period = min_period
47
+ self.max_period = max_period
48
+ self.D_head = D_head
49
+ self.normalize_coords = normalize_coords
50
+ self.shift_coords = shift_coords
51
+ self.jitter_coords = jitter_coords
52
+ self.rescale_coords = rescale_coords
53
+
54
+ # Needs persistent=True because we do teacher.load_state_dict(student.state_dict()) to initialize the teacher
55
+ self.dtype = dtype # Don't rely on self.periods.dtype
56
+ self.register_buffer(
57
+ "periods",
58
+ torch.empty(D_head // 4, device=device, dtype=dtype),
59
+ persistent=True,
60
+ )
61
+ self._init_weights()
62
+
63
+ def forward(self, *, H: int, W: int) -> tuple[Tensor, Tensor]:
64
+ device = self.periods.device
65
+ dtype = self.dtype
66
+ dd = {"device": device, "dtype": dtype}
67
+
68
+ # Prepare coords in range [-1, +1]
69
+ if self.normalize_coords == "max":
70
+ max_HW = max(H, W)
71
+ coords_h = torch.arange(0.5, H, **dd) / max_HW # [H]
72
+ coords_w = torch.arange(0.5, W, **dd) / max_HW # [W]
73
+ elif self.normalize_coords == "min":
74
+ min_HW = min(H, W)
75
+ coords_h = torch.arange(0.5, H, **dd) / min_HW # [H]
76
+ coords_w = torch.arange(0.5, W, **dd) / min_HW # [W]
77
+ elif self.normalize_coords == "separate":
78
+ coords_h = torch.arange(0.5, H, **dd) / H # [H]
79
+ coords_w = torch.arange(0.5, W, **dd) / W # [W]
80
+ else:
81
+ raise ValueError(f"Unknown normalize_coords: {self.normalize_coords}")
82
+ coords = torch.stack(torch.meshgrid(coords_h, coords_w, indexing="ij"), dim=-1) # [H, W, 2]
83
+ coords = coords.flatten(0, 1) # [HW, 2]
84
+ coords = 2.0 * coords - 1.0 # Shift range [0, 1] to [-1, +1]
85
+
86
+ # Shift coords by adding a uniform value in [-shift, shift]
87
+ if self.training and self.shift_coords is not None:
88
+ shift_hw = torch.empty(2, **dd).uniform_(-self.shift_coords, self.shift_coords)
89
+ coords += shift_hw[None, :]
90
+
91
+ # Jitter coords by multiplying the range [-1, 1] by a log-uniform value in [1/jitter, jitter]
92
+ if self.training and self.jitter_coords is not None:
93
+ jitter_max = np.log(self.jitter_coords)
94
+ jitter_min = -jitter_max
95
+ jitter_hw = torch.empty(2, **dd).uniform_(jitter_min, jitter_max).exp()
96
+ coords *= jitter_hw[None, :]
97
+
98
+ # Rescale coords by multiplying the range [-1, 1] by a log-uniform value in [1/rescale, rescale]
99
+ if self.training and self.rescale_coords is not None:
100
+ rescale_max = np.log(self.rescale_coords)
101
+ rescale_min = -rescale_max
102
+ rescale_hw = torch.empty(1, **dd).uniform_(rescale_min, rescale_max).exp()
103
+ coords *= rescale_hw
104
+
105
+ # Prepare angles and sin/cos
106
+ angles = 2 * math.pi * coords[:, :, None] / self.periods[None, None, :] # [HW, 2, D//4]
107
+ angles = angles.flatten(1, 2) # [HW, D//2]
108
+ angles = angles.tile(2) # [HW, D]
109
+ cos = torch.cos(angles) # [HW, D]
110
+ sin = torch.sin(angles) # [HW, D]
111
+
112
+ return (sin, cos) # 2 * [HW, D]
113
+
114
+ def _init_weights(self):
115
+ device = self.periods.device
116
+ dtype = self.dtype
117
+ if self.base is not None:
118
+ periods = self.base ** (
119
+ 2 * torch.arange(self.D_head // 4, device=device, dtype=dtype) / (self.D_head // 2)
120
+ ) # [D//4]
121
+ else:
122
+ base = self.max_period / self.min_period
123
+ exponents = torch.linspace(0, 1, self.D_head // 4, device=device, dtype=dtype) # [D//4] range [0, 1]
124
+ periods = base**exponents # range [1, max_period / min_period]
125
+ periods = periods / base # range [min_period / max_period, 1]
126
+ periods = periods * self.max_period # range [min_period, max_period]
127
+ self.periods.data = periods
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/utils.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
8
+ #
9
+ # This software may be used and distributed in accordance with
10
+ # the terms of the DINOv3 License Agreement.
11
+
12
+ import logging
13
+ import os
14
+ import random
15
+ import subprocess
16
+ from typing import Callable, List, Optional, Tuple
17
+
18
+ import numpy as np
19
+ import torch
20
+ from torch import Tensor, nn
21
+
22
+ logger = logging.getLogger("dinov3")
23
+
24
+
25
+ def cat_keep_shapes(x_list: List[Tensor]) -> Tuple[Tensor, List[Tuple[int]], List[int]]:
26
+ shapes = [x.shape for x in x_list]
27
+ num_tokens = [x.select(dim=-1, index=0).numel() for x in x_list]
28
+ flattened = torch.cat([x.flatten(0, -2) for x in x_list])
29
+ return flattened, shapes, num_tokens
30
+
31
+
32
+ def uncat_with_shapes(flattened: Tensor, shapes: List[Tuple[int]], num_tokens: List[int]) -> List[Tensor]:
33
+ outputs_splitted = torch.split_with_sizes(flattened, num_tokens, dim=0)
34
+ shapes_adjusted = [shape[:-1] + torch.Size([flattened.shape[-1]]) for shape in shapes]
35
+ outputs_reshaped = [o.reshape(shape) for o, shape in zip(outputs_splitted, shapes_adjusted)]
36
+ return outputs_reshaped
37
+
38
+
39
+ def named_replace(
40
+ fn: Callable,
41
+ module: nn.Module,
42
+ name: str = "",
43
+ depth_first: bool = True,
44
+ include_root: bool = False,
45
+ ) -> nn.Module:
46
+ if not depth_first and include_root:
47
+ module = fn(module=module, name=name)
48
+ for child_name_o, child_module in list(module.named_children()):
49
+ child_name = ".".join((name, child_name_o)) if name else child_name_o
50
+ new_child = named_replace(
51
+ fn=fn,
52
+ module=child_module,
53
+ name=child_name,
54
+ depth_first=depth_first,
55
+ include_root=True,
56
+ )
57
+ setattr(module, child_name_o, new_child)
58
+
59
+ if depth_first and include_root:
60
+ module = fn(module=module, name=name)
61
+ return module
62
+
63
+
64
+ def named_apply(
65
+ fn: Callable,
66
+ module: nn.Module,
67
+ name: str = "",
68
+ depth_first: bool = True,
69
+ include_root: bool = False,
70
+ ) -> nn.Module:
71
+ if not depth_first and include_root:
72
+ fn(module=module, name=name)
73
+ for child_name, child_module in module.named_children():
74
+ child_name = ".".join((name, child_name)) if name else child_name
75
+ named_apply(
76
+ fn=fn,
77
+ module=child_module,
78
+ name=child_name,
79
+ depth_first=depth_first,
80
+ include_root=True,
81
+ )
82
+ if depth_first and include_root:
83
+ fn(module=module, name=name)
84
+ return module
85
+
86
+
87
+ def fix_random_seeds(seed: int = 31):
88
+ """
89
+ Fix random seeds.
90
+ """
91
+ torch.manual_seed(seed)
92
+ torch.cuda.manual_seed_all(seed)
93
+ np.random.seed(seed)
94
+ random.seed(seed)
95
+
96
+
97
+ def get_sha() -> str:
98
+ cwd = os.path.dirname(os.path.abspath(__file__))
99
+
100
+ def _run(command):
101
+ return subprocess.check_output(command, cwd=cwd).decode("ascii").strip()
102
+
103
+ sha = "N/A"
104
+ diff = "clean"
105
+ branch = "N/A"
106
+ try:
107
+ sha = _run(["git", "rev-parse", "HEAD"])
108
+ subprocess.check_output(["git", "diff"], cwd=cwd)
109
+ diff = _run(["git", "diff-index", "HEAD"])
110
+ diff = "has uncommited changes" if diff else "clean"
111
+ branch = _run(["git", "rev-parse", "--abbrev-ref", "HEAD"])
112
+ except Exception:
113
+ pass
114
+ message = f"sha: {sha}, status: {diff}, branch: {branch}"
115
+ return message
116
+
117
+
118
+ def get_conda_env() -> Tuple[Optional[str], Optional[str]]:
119
+ conda_env_name = os.environ.get("CONDA_DEFAULT_ENV")
120
+ conda_env_path = os.environ.get("CONDA_PREFIX")
121
+ return conda_env_name, conda_env_path
122
+
123
+
124
+ def count_parameters(module: nn.Module) -> int:
125
+ c = 0
126
+ for m in module.parameters():
127
+ c += m.nelement()
128
+ return c
129
+
130
+
131
+ def has_batchnorms(model: nn.Module) -> bool:
132
+ bn_types = (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.SyncBatchNorm)
133
+ for _, module in model.named_modules():
134
+ if isinstance(module, bn_types):
135
+ return True
136
+ return False
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/vision_transformer.py ADDED
@@ -0,0 +1,424 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+ #
7
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
8
+ #
9
+ # This software may be used and distributed in accordance with
10
+ # the terms of the DINOv3 License Agreement.
11
+
12
+ import logging
13
+ from functools import partial
14
+ from typing import Any, Dict, List, Literal, Optional, Sequence, Tuple, Union
15
+
16
+ import torch
17
+ import torch.nn.init
18
+ from torch import Tensor, nn
19
+
20
+ from . import LayerScale, Mlp, PatchEmbed, RMSNorm, RopePositionEmbedding, SelfAttentionBlock, SwiGLUFFN
21
+ from .utils import named_apply
22
+
23
+ logger = logging.getLogger("dinov3")
24
+
25
+ ffn_layer_dict = {
26
+ "mlp": Mlp,
27
+ "swiglu": SwiGLUFFN,
28
+ "swiglu32": partial(SwiGLUFFN, align_to=32),
29
+ "swiglu64": partial(SwiGLUFFN, align_to=64),
30
+ "swiglu128": partial(SwiGLUFFN, align_to=128),
31
+ }
32
+
33
+ norm_layer_dict = {
34
+ "layernorm": partial(nn.LayerNorm, eps=1e-6),
35
+ "layernormbf16": partial(nn.LayerNorm, eps=1e-5),
36
+ "rmsnorm": RMSNorm,
37
+ }
38
+
39
+ dtype_dict = {
40
+ "fp32": torch.float32,
41
+ "fp16": torch.float16,
42
+ "bf16": torch.bfloat16,
43
+ }
44
+
45
+
46
+ def init_weights_vit(module: nn.Module, name: str = ""):
47
+ if isinstance(module, nn.Linear):
48
+ torch.nn.init.trunc_normal_(module.weight, std=0.02)
49
+ if module.bias is not None:
50
+ nn.init.zeros_(module.bias)
51
+ if hasattr(module, "bias_mask") and module.bias_mask is not None:
52
+ o = module.out_features
53
+ module.bias_mask.fill_(1)
54
+ module.bias_mask[o // 3 : 2 * o // 3].fill_(0)
55
+ if isinstance(module, nn.LayerNorm):
56
+ module.reset_parameters()
57
+ if isinstance(module, LayerScale):
58
+ module.reset_parameters()
59
+ if isinstance(module, PatchEmbed):
60
+ module.reset_parameters()
61
+ if isinstance(module, RMSNorm):
62
+ module.reset_parameters()
63
+
64
+
65
+ class DinoVisionTransformer(nn.Module):
66
+ def __init__(
67
+ self,
68
+ *,
69
+ img_size: int = 224,
70
+ patch_size: int = 16,
71
+ in_chans: int = 3,
72
+ pos_embed_rope_base: float = 100.0,
73
+ pos_embed_rope_min_period: float | None = None,
74
+ pos_embed_rope_max_period: float | None = None,
75
+ pos_embed_rope_normalize_coords: Literal["min", "max", "separate"] = "separate",
76
+ pos_embed_rope_shift_coords: float | None = None,
77
+ pos_embed_rope_jitter_coords: float | None = None,
78
+ pos_embed_rope_rescale_coords: float | None = None,
79
+ pos_embed_rope_dtype: str = "bf16",
80
+ embed_dim: int = 768,
81
+ depth: int = 12,
82
+ num_heads: int = 12,
83
+ ffn_ratio: float = 4.0,
84
+ qkv_bias: bool = True,
85
+ drop_path_rate: float = 0.0,
86
+ layerscale_init: float | None = None,
87
+ norm_layer: str = "layernorm",
88
+ ffn_layer: str = "mlp",
89
+ ffn_bias: bool = True,
90
+ proj_bias: bool = True,
91
+ n_storage_tokens: int = 0,
92
+ mask_k_bias: bool = False,
93
+ untie_cls_and_patch_norms: bool = False,
94
+ untie_global_and_local_cls_norm: bool = False,
95
+ device: Any | None = None,
96
+ **ignored_kwargs,
97
+ ):
98
+ super().__init__()
99
+ if len(ignored_kwargs) > 0:
100
+ logger.warning(f"Ignored kwargs: {ignored_kwargs}")
101
+ del ignored_kwargs
102
+
103
+ norm_layer_cls = norm_layer_dict[norm_layer]
104
+
105
+ self.num_features = self.embed_dim = embed_dim # num_features for consistency with other models
106
+ self.n_blocks = depth
107
+ self.num_heads = num_heads
108
+ self.patch_size = patch_size
109
+
110
+ self.patch_embed = PatchEmbed(
111
+ img_size=img_size,
112
+ patch_size=patch_size,
113
+ in_chans=in_chans,
114
+ embed_dim=embed_dim,
115
+ flatten_embedding=False,
116
+ )
117
+
118
+ self.cls_token = nn.Parameter(torch.empty(1, 1, embed_dim, device=device))
119
+ self.n_storage_tokens = n_storage_tokens
120
+ if self.n_storage_tokens > 0:
121
+ self.storage_tokens = nn.Parameter(torch.empty(1, n_storage_tokens, embed_dim, device=device))
122
+ logger.info(f"using base={pos_embed_rope_base} for rope new")
123
+ logger.info(f"using min_period={pos_embed_rope_min_period} for rope new")
124
+ logger.info(f"using max_period={pos_embed_rope_max_period} for rope new")
125
+ logger.info(f"using normalize_coords={pos_embed_rope_normalize_coords} for rope new")
126
+ logger.info(f"using shift_coords={pos_embed_rope_shift_coords} for rope new")
127
+ logger.info(f"using rescale_coords={pos_embed_rope_rescale_coords} for rope new")
128
+ logger.info(f"using jitter_coords={pos_embed_rope_jitter_coords} for rope new")
129
+ logger.info(f"using dtype={pos_embed_rope_dtype} for rope new")
130
+ self.rope_embed = RopePositionEmbedding(
131
+ embed_dim=embed_dim,
132
+ num_heads=num_heads,
133
+ base=pos_embed_rope_base,
134
+ min_period=pos_embed_rope_min_period,
135
+ max_period=pos_embed_rope_max_period,
136
+ normalize_coords=pos_embed_rope_normalize_coords,
137
+ shift_coords=pos_embed_rope_shift_coords,
138
+ jitter_coords=pos_embed_rope_jitter_coords,
139
+ rescale_coords=pos_embed_rope_rescale_coords,
140
+ dtype=dtype_dict[pos_embed_rope_dtype],
141
+ device=device,
142
+ )
143
+ logger.info(f"using {ffn_layer} layer as FFN")
144
+ ffn_layer_cls = ffn_layer_dict[ffn_layer]
145
+ ffn_ratio_sequence = [ffn_ratio] * depth
146
+ blocks_list = [
147
+ SelfAttentionBlock(
148
+ dim=embed_dim,
149
+ num_heads=num_heads,
150
+ ffn_ratio=ffn_ratio_sequence[i],
151
+ qkv_bias=qkv_bias,
152
+ proj_bias=proj_bias,
153
+ ffn_bias=ffn_bias,
154
+ drop_path=drop_path_rate,
155
+ norm_layer=norm_layer_cls,
156
+ act_layer=nn.GELU,
157
+ ffn_layer=ffn_layer_cls,
158
+ init_values=layerscale_init,
159
+ mask_k_bias=mask_k_bias,
160
+ device=device,
161
+ )
162
+ for i in range(depth)
163
+ ]
164
+
165
+ self.chunked_blocks = False
166
+ self.blocks = nn.ModuleList(blocks_list)
167
+
168
+ # This norm is applied to everything, or when untying, to patch and mask tokens.
169
+ self.norm = norm_layer_cls(embed_dim)
170
+
171
+ self.untie_cls_and_patch_norms = untie_cls_and_patch_norms
172
+ if untie_cls_and_patch_norms:
173
+ # When untying, this norm is applied to CLS tokens and registers.
174
+ self.cls_norm = norm_layer_cls(embed_dim)
175
+ else:
176
+ self.cls_norm = None
177
+
178
+ self.untie_global_and_local_cls_norm = untie_global_and_local_cls_norm
179
+ if untie_global_and_local_cls_norm:
180
+ # When untying, this norm is applied to local CLS tokens and registers.
181
+ # This norm is never used during eval.
182
+ self.local_cls_norm = norm_layer_cls(embed_dim)
183
+ else:
184
+ self.local_cls_norm = None
185
+ self.head = nn.Identity()
186
+ self.mask_token = nn.Parameter(torch.empty(1, embed_dim, device=device))
187
+
188
+ def init_weights(self):
189
+ self.rope_embed._init_weights()
190
+ nn.init.normal_(self.cls_token, std=0.02)
191
+ if self.n_storage_tokens > 0:
192
+ nn.init.normal_(self.storage_tokens, std=0.02)
193
+ nn.init.zeros_(self.mask_token)
194
+ named_apply(init_weights_vit, self)
195
+
196
+ def prepare_tokens_with_masks(self, x: Tensor, masks=None) -> Tuple[Tensor, Tuple[int]]:
197
+ x = self.patch_embed(x)
198
+ B, H, W, _ = x.shape
199
+ x = x.flatten(1, 2)
200
+
201
+ if masks is not None:
202
+ x = torch.where(masks.unsqueeze(-1), self.mask_token.to(x.dtype).unsqueeze(0), x)
203
+ cls_token = self.cls_token
204
+ else:
205
+ cls_token = self.cls_token + 0 * self.mask_token
206
+ if self.n_storage_tokens > 0:
207
+ storage_tokens = self.storage_tokens
208
+ else:
209
+ storage_tokens = torch.empty(
210
+ 1,
211
+ 0,
212
+ cls_token.shape[-1],
213
+ dtype=cls_token.dtype,
214
+ device=cls_token.device,
215
+ )
216
+
217
+ x = torch.cat(
218
+ [
219
+ cls_token.expand(B, -1, -1),
220
+ storage_tokens.expand(B, -1, -1),
221
+ x,
222
+ ],
223
+ dim=1,
224
+ )
225
+
226
+ return x, (H, W)
227
+
228
+ def forward_features_list(self, x_list: List[Tensor], masks_list: List[Tensor]) -> List[Dict[str, Tensor]]:
229
+ x = []
230
+ rope = []
231
+ for t_x, t_masks in zip(x_list, masks_list):
232
+ t2_x, hw_tuple = self.prepare_tokens_with_masks(t_x, t_masks)
233
+ x.append(t2_x)
234
+ rope.append(hw_tuple)
235
+ for _, blk in enumerate(self.blocks):
236
+ if self.rope_embed is not None:
237
+ rope_sincos = [self.rope_embed(H=H, W=W) for H, W in rope]
238
+ else:
239
+ rope_sincos = [None for r in rope]
240
+ x = blk(x, rope_sincos)
241
+ all_x = x
242
+ output = []
243
+ for idx, (x, masks) in enumerate(zip(all_x, masks_list)):
244
+ if self.untie_cls_and_patch_norms or self.untie_global_and_local_cls_norm:
245
+ if self.untie_global_and_local_cls_norm and self.training and idx == 1:
246
+ # Assume second entry of list corresponds to local crops.
247
+ # We only ever apply this during training.
248
+ x_norm_cls_reg = self.local_cls_norm(x[:, : self.n_storage_tokens + 1])
249
+ elif self.untie_cls_and_patch_norms:
250
+ x_norm_cls_reg = self.cls_norm(x[:, : self.n_storage_tokens + 1])
251
+ else:
252
+ x_norm_cls_reg = self.norm(x[:, : self.n_storage_tokens + 1])
253
+ x_norm_patch = self.norm(x[:, self.n_storage_tokens + 1 :])
254
+ else:
255
+ x_norm = self.norm(x)
256
+ x_norm_cls_reg = x_norm[:, : self.n_storage_tokens + 1]
257
+ x_norm_patch = x_norm[:, self.n_storage_tokens + 1 :]
258
+ output.append(
259
+ {
260
+ "x_norm_clstoken": x_norm_cls_reg[:, 0],
261
+ "x_storage_tokens": x_norm_cls_reg[:, 1:],
262
+ "x_norm_patchtokens": x_norm_patch,
263
+ "x_prenorm": x,
264
+ "masks": masks,
265
+ }
266
+ )
267
+ return output
268
+
269
+ def forward_features(self, x: Tensor | List[Tensor], masks: Optional[Tensor] = None) -> List[Dict[str, Tensor]]:
270
+ if isinstance(x, torch.Tensor):
271
+ return self.forward_features_list([x], [masks])[0]
272
+ else:
273
+ return self.forward_features_list(x, masks)
274
+
275
+ def _get_intermediate_layers_not_chunked(self, x: Tensor, n: int = 1) -> List[Tensor]:
276
+ x, (H, W) = self.prepare_tokens_with_masks(x)
277
+ # If n is an int, take the n last blocks. If it's a list, take them
278
+ output, total_block_len = [], len(self.blocks)
279
+ blocks_to_take = range(total_block_len - n, total_block_len) if isinstance(n, int) else n
280
+ for i, blk in enumerate(self.blocks):
281
+ if self.rope_embed is not None:
282
+ rope_sincos = self.rope_embed(H=H, W=W)
283
+ else:
284
+ rope_sincos = None
285
+ x = blk(x, rope_sincos)
286
+ if i in blocks_to_take:
287
+ output.append(x)
288
+ assert len(output) == len(blocks_to_take), f"only {len(output)} / {len(blocks_to_take)} blocks found"
289
+ return output
290
+
291
+ def get_intermediate_layers(
292
+ self,
293
+ x: torch.Tensor,
294
+ *,
295
+ n: Union[int, Sequence] = 1, # Layers or n last layers to take
296
+ reshape: bool = False,
297
+ return_class_token: bool = False,
298
+ return_extra_tokens: bool = False,
299
+ norm: bool = True,
300
+ ) -> Tuple[Union[torch.Tensor, Tuple[torch.Tensor, ...]]]:
301
+ outputs = self._get_intermediate_layers_not_chunked(x, n)
302
+ if norm:
303
+ outputs_normed = []
304
+ for out in outputs:
305
+ if self.untie_cls_and_patch_norms:
306
+ x_norm_cls_reg = self.cls_norm(out[:, : self.n_storage_tokens + 1])
307
+ x_norm_patch = self.norm(out[:, self.n_storage_tokens + 1 :])
308
+ outputs_normed.append(torch.cat((x_norm_cls_reg, x_norm_patch), dim=1))
309
+ else:
310
+ outputs_normed.append(self.norm(out))
311
+ outputs = outputs_normed
312
+ class_tokens = [out[:, 0] for out in outputs]
313
+ extra_tokens = [out[:, 1 : self.n_storage_tokens + 1] for out in outputs]
314
+ outputs = [out[:, self.n_storage_tokens + 1 :] for out in outputs]
315
+ if reshape:
316
+ B, _, h, w = x.shape
317
+ outputs = [
318
+ out.reshape(B, h // self.patch_size, w // self.patch_size, -1).permute(0, 3, 1, 2).contiguous()
319
+ for out in outputs
320
+ ]
321
+ if not return_class_token and not return_extra_tokens:
322
+ return tuple(outputs)
323
+ elif return_class_token and not return_extra_tokens:
324
+ return tuple(zip(outputs, class_tokens))
325
+ elif not return_class_token and return_extra_tokens:
326
+ return tuple(zip(outputs, extra_tokens))
327
+ elif return_class_token and return_extra_tokens:
328
+ return tuple(zip(outputs, class_tokens, extra_tokens))
329
+
330
+ def forward(self, *args, is_training: bool = True, **kwargs) -> List[Dict[str, Tensor]] | Tensor:
331
+ # VGGT-Omega change: the aggregator consumes DINOv3 patch-token
332
+ # features directly, so the default forward returns the feature dict.
333
+ ret = self.forward_features(*args, **kwargs)
334
+ if is_training:
335
+ return ret
336
+ else:
337
+ return self.head(ret["x_norm_clstoken"])
338
+
339
+
340
+ def vit_small(patch_size=16, **kwargs):
341
+ model = DinoVisionTransformer(
342
+ patch_size=patch_size,
343
+ embed_dim=384,
344
+ depth=12,
345
+ num_heads=6,
346
+ ffn_ratio=4,
347
+ **kwargs,
348
+ )
349
+ return model
350
+
351
+
352
+ def vit_base(patch_size=16, **kwargs):
353
+ model = DinoVisionTransformer(
354
+ patch_size=patch_size,
355
+ embed_dim=768,
356
+ depth=12,
357
+ num_heads=12,
358
+ ffn_ratio=4,
359
+ **kwargs,
360
+ )
361
+ return model
362
+
363
+
364
+ def vit_large(patch_size=16, **kwargs):
365
+ model = DinoVisionTransformer(
366
+ patch_size=patch_size,
367
+ embed_dim=1024,
368
+ depth=24,
369
+ num_heads=16,
370
+ ffn_ratio=4,
371
+ **kwargs,
372
+ )
373
+ return model
374
+
375
+
376
+ def vit_so400m(patch_size=16, **kwargs):
377
+ model = DinoVisionTransformer(
378
+ patch_size=patch_size,
379
+ embed_dim=1152,
380
+ depth=27,
381
+ num_heads=18,
382
+ ffn_ratio=3.777777778,
383
+ **kwargs,
384
+ )
385
+ return model
386
+
387
+
388
+ def vit_huge2(patch_size=16, **kwargs):
389
+ model = DinoVisionTransformer(
390
+ patch_size=patch_size,
391
+ embed_dim=1280,
392
+ depth=32,
393
+ num_heads=20,
394
+ ffn_ratio=4,
395
+ **kwargs,
396
+ )
397
+ return model
398
+
399
+
400
+ def vit_giant2(patch_size=16, **kwargs):
401
+ """
402
+ Close to ViT-giant, with embed-dim 1536 and 24 heads => embed-dim per head 64
403
+ """
404
+ model = DinoVisionTransformer(
405
+ patch_size=patch_size,
406
+ embed_dim=1536,
407
+ depth=40,
408
+ num_heads=24,
409
+ ffn_ratio=4,
410
+ **kwargs,
411
+ )
412
+ return model
413
+
414
+
415
+ def vit_7b(patch_size=16, **kwargs):
416
+ model = DinoVisionTransformer(
417
+ patch_size=patch_size,
418
+ embed_dim=4096,
419
+ depth=40,
420
+ num_heads=32,
421
+ ffn_ratio=3,
422
+ **kwargs,
423
+ )
424
+ return model
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/vggt_omega.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ import warnings
8
+
9
+ import torch
10
+ import torch.nn as nn
11
+
12
+ from vggt_omega.models.aggregator import Aggregator
13
+ from vggt_omega.models.heads import CameraHead, DenseHead, TextAlignmentHead
14
+
15
+
16
+ class VGGTOmega(nn.Module):
17
+ """Minimal VGGT-Omega inference model for camera and depth prediction."""
18
+
19
+ def __init__(
20
+ self,
21
+ patch_size: int = 16,
22
+ embed_dim: int = 1024,
23
+ enable_camera: bool = True,
24
+ enable_depth: bool = True,
25
+ enable_alignment: bool = False,
26
+ ) -> None:
27
+ super().__init__()
28
+
29
+ self.aggregator = Aggregator(patch_size=patch_size, embed_dim=embed_dim)
30
+ _warn_if_rope_not_max(self.aggregator)
31
+ self.camera_head = CameraHead(dim_in=2 * embed_dim) if enable_camera else None
32
+ self.dense_head = DenseHead(dim_in=2 * embed_dim, patch_size=patch_size) if enable_depth else None
33
+ self.text_alignment_head = TextAlignmentHead(dim_in=2 * embed_dim) if enable_alignment else None
34
+
35
+ def forward(self, images: torch.Tensor) -> dict[str, torch.Tensor]:
36
+ if len(images.shape) == 4:
37
+ images = images.unsqueeze(0)
38
+
39
+ amp_dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
40
+ with torch.autocast(device_type="cuda", dtype=amp_dtype):
41
+ aggregated_tokens_list, patch_token_start = self.aggregator(images)
42
+
43
+ final_tokens = aggregated_tokens_list[-1]
44
+ if final_tokens is None:
45
+ raise ValueError("Aggregator did not cache the final layer, which VGGTOmega needs.")
46
+
47
+ predictions = {
48
+ "camera_and_register_tokens": final_tokens[:, :, :patch_token_start].contiguous(),
49
+ }
50
+ with torch.autocast(device_type="cuda", enabled=False):
51
+ if self.camera_head is not None:
52
+ predictions["pose_enc"] = self.camera_head(
53
+ aggregated_tokens_list,
54
+ patch_token_start=patch_token_start,
55
+ )
56
+
57
+ if self.dense_head is not None:
58
+ depth, depth_conf = self.dense_head(
59
+ aggregated_tokens_list,
60
+ images=images,
61
+ patch_token_start=patch_token_start,
62
+ )
63
+ predictions["depth"] = depth
64
+ predictions["depth_conf"] = depth_conf
65
+
66
+ if self.text_alignment_head is not None:
67
+ predictions.update(
68
+ self.text_alignment_head(
69
+ aggregated_tokens_list,
70
+ patch_token_start=patch_token_start,
71
+ )
72
+ )
73
+
74
+ if not self.training:
75
+ predictions["images"] = images
76
+ return predictions
77
+
78
+
79
+ def _warn_if_rope_not_max(aggregator: nn.Module) -> None:
80
+ for name, module in (("aggregator.patch_embed", aggregator.patch_embed), ("aggregator", aggregator)):
81
+ rope_embed = getattr(module, "rope_embed", None)
82
+ normalize_coords = getattr(rope_embed, "normalize_coords", None)
83
+ if normalize_coords != "max":
84
+ warnings.warn(
85
+ f"{name} RoPE normalize_coords is {normalize_coords!r}; "
86
+ "the released VGGT-Omega checkpoint was trained with 'max'.",
87
+ stacklevel=2,
88
+ )
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """Camera and geometry utilities for VGGT-Omega."""
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/geometry.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ import numpy as np
8
+ import torch
9
+
10
+
11
+ def closed_form_inverse_se3(se3, R=None, T=None):
12
+ """Invert a batch of 3x4 or 4x4 SE(3) matrices."""
13
+ is_numpy = isinstance(se3, np.ndarray)
14
+
15
+ if se3.shape[-2:] != (4, 4) and se3.shape[-2:] != (3, 4):
16
+ raise ValueError(f"se3 must have shape (N, 4, 4) or (N, 3, 4), got {se3.shape}")
17
+
18
+ if R is None:
19
+ R = se3[:, :3, :3]
20
+ if T is None:
21
+ T = se3[:, :3, 3:]
22
+
23
+ if is_numpy:
24
+ R_t = np.transpose(R, (0, 2, 1))
25
+ top_right = -np.matmul(R_t, T)
26
+ inverted = np.tile(np.eye(4), (len(R), 1, 1))
27
+ else:
28
+ R_t = R.transpose(1, 2)
29
+ top_right = -torch.bmm(R_t, T)
30
+ inverted = torch.eye(4, device=R.device, dtype=R.dtype)[None].repeat(len(R), 1, 1)
31
+
32
+ inverted[:, :3, :3] = R_t
33
+ inverted[:, :3, 3:] = top_right
34
+ return inverted
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/load_fn.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ import warnings
8
+
9
+ import numpy as np
10
+ import torch
11
+ from PIL import Image
12
+ from torchvision import transforms as TF
13
+
14
+
15
+ def load_and_preprocess_images(image_path_list, mode="balanced", image_resolution=512, patch_size=16):
16
+ """Load images for VGGT-Omega inference.
17
+
18
+ `balanced` keeps the total token count close to image_resolution**2.
19
+ `max_size` resizes the longest side to image_resolution.
20
+ Both modes first center-crop extreme aspect ratios into [0.5, 2.0].
21
+ """
22
+ if len(image_path_list) == 0:
23
+ raise ValueError("At least 1 image is required")
24
+ if mode not in ["balanced", "max_size"]:
25
+ raise ValueError("Mode must be either 'balanced' or 'max_size'")
26
+ if image_resolution <= 0:
27
+ raise ValueError("image_resolution must be positive")
28
+ if patch_size <= 0:
29
+ raise ValueError("patch_size must be positive")
30
+ if image_resolution % patch_size != 0:
31
+ raise ValueError("image_resolution must be divisible by patch_size")
32
+
33
+ images = []
34
+ shapes = set()
35
+ to_tensor = TF.ToTensor()
36
+
37
+ for image_path in image_path_list:
38
+ image = _crop_to_supported_aspect_ratio(_load_rgb_image(image_path))
39
+ width, height = image.size
40
+ aspect_ratio = height / max(width, 1)
41
+
42
+ if mode == "balanced":
43
+ target_h, target_w = _balanced_target_shape(aspect_ratio, image_resolution, patch_size)
44
+ else:
45
+ target_h, target_w = _max_size_target_shape(aspect_ratio, image_resolution, patch_size)
46
+
47
+ image = image.resize((target_w, target_h), Image.Resampling.BICUBIC)
48
+ image = to_tensor(image)
49
+
50
+ shapes.add((image.shape[1], image.shape[2]))
51
+ images.append(image)
52
+
53
+ if len(shapes) > 1:
54
+ warnings.warn(f"Found images with different shapes: {shapes}; padding to a common size.", stacklevel=2)
55
+ images = _pad_images_to_common_size(images, shapes)
56
+
57
+ return torch.stack(images)
58
+
59
+
60
+ def _load_rgb_image(image_path):
61
+ with Image.open(image_path) as image:
62
+ if image.mode == "RGBA":
63
+ background = Image.new("RGBA", image.size, (255, 255, 255, 255))
64
+ image = Image.alpha_composite(background, image)
65
+ return image.convert("RGB")
66
+
67
+
68
+ def _crop_to_supported_aspect_ratio(image, min_aspect_ratio=0.5, max_aspect_ratio=2.0):
69
+ width, height = image.size
70
+ aspect_ratio = height / max(width, 1)
71
+
72
+ if aspect_ratio < min_aspect_ratio:
73
+ crop_width = min(width, max(1, int(round(height / min_aspect_ratio))))
74
+ left = max((width - crop_width) // 2, 0)
75
+ return image.crop((left, 0, left + crop_width, height))
76
+
77
+ if aspect_ratio > max_aspect_ratio:
78
+ crop_height = min(height, max(1, int(round(width * max_aspect_ratio))))
79
+ top = max((height - crop_height) // 2, 0)
80
+ return image.crop((0, top, width, top + crop_height))
81
+
82
+ return image
83
+
84
+
85
+ def _balanced_target_shape(aspect_ratio, image_resolution, patch_size):
86
+ token_number = (image_resolution // patch_size) ** 2
87
+ w_patches = np.sqrt(token_number / aspect_ratio)
88
+ h_patches = token_number / w_patches
89
+ w_patches = max(1, int(np.round(w_patches)))
90
+ h_patches = max(1, int(np.round(h_patches)))
91
+ return h_patches * patch_size, w_patches * patch_size
92
+
93
+
94
+ def _max_size_target_shape(aspect_ratio, image_resolution, patch_size):
95
+ if aspect_ratio >= 1.0:
96
+ height = image_resolution
97
+ width = _round_to_patch_multiple(image_resolution / aspect_ratio, patch_size)
98
+ else:
99
+ width = image_resolution
100
+ height = _round_to_patch_multiple(image_resolution * aspect_ratio, patch_size)
101
+ return height, width
102
+
103
+
104
+ def _round_to_patch_multiple(value, patch_size):
105
+ return max(patch_size, int(np.round(float(value) / patch_size)) * patch_size)
106
+
107
+
108
+ def _pad_images_to_common_size(images, shapes):
109
+ max_height = max(shape[0] for shape in shapes)
110
+ max_width = max(shape[1] for shape in shapes)
111
+
112
+ padded_images = []
113
+ for image in images:
114
+ h_padding = max_height - image.shape[1]
115
+ w_padding = max_width - image.shape[2]
116
+ if h_padding > 0 or w_padding > 0:
117
+ pad_top = h_padding // 2
118
+ pad_bottom = h_padding - pad_top
119
+ pad_left = w_padding // 2
120
+ pad_right = w_padding - pad_left
121
+ image = torch.nn.functional.pad(
122
+ image,
123
+ (pad_left, pad_right, pad_top, pad_bottom),
124
+ mode="constant",
125
+ value=1.0,
126
+ )
127
+ padded_images.append(image)
128
+
129
+ return padded_images
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/pose_enc.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ import torch
8
+
9
+ from .rotation import mat_to_quat, quat_to_mat
10
+
11
+
12
+ def extri_intri_to_pose_encoding(extrinsics, intrinsics, image_size_hw):
13
+ """Convert camera extrinsics and intrinsics to VGGT-Omega pose encoding.
14
+
15
+ The released checkpoints use a 9D camera encoding:
16
+ translation (3), quaternion rotation (4), and vertical/horizontal FoV (2).
17
+ Extrinsics are camera-from-world matrices in OpenCV coordinates.
18
+ """
19
+ R = extrinsics[:, :, :3, :3]
20
+ T = extrinsics[:, :, :3, 3]
21
+
22
+ H, W = image_size_hw
23
+ quat = mat_to_quat(R)
24
+ fov_h = 2 * torch.atan((H / 2) / intrinsics[..., 1, 1])
25
+ fov_w = 2 * torch.atan((W / 2) / intrinsics[..., 0, 0])
26
+ return torch.cat([T, quat, fov_h[..., None], fov_w[..., None]], dim=-1).float()
27
+
28
+
29
+ def encoding_to_camera(pose_encoding, image_size_hw, build_intrinsics=True):
30
+ """Decode VGGT-Omega pose encoding into extrinsics and intrinsics."""
31
+ T = pose_encoding[..., :3]
32
+ quat = pose_encoding[..., 3:7]
33
+ fov_h = pose_encoding[..., 7]
34
+ fov_w = pose_encoding[..., 8]
35
+
36
+ R = quat_to_mat(quat)
37
+ extrinsics = torch.cat([R, T[..., None]], dim=-1)
38
+
39
+ intrinsics = None
40
+ if build_intrinsics:
41
+ H, W = image_size_hw
42
+ fy = (H / 2.0) / torch.tan(fov_h / 2.0)
43
+ fx = (W / 2.0) / torch.tan(fov_w / 2.0)
44
+
45
+ intrinsics = torch.zeros(pose_encoding.shape[:2] + (3, 3), device=pose_encoding.device)
46
+ intrinsics[..., 0, 0] = fx
47
+ intrinsics[..., 1, 1] = fy
48
+ intrinsics[..., 0, 2] = W / 2
49
+ intrinsics[..., 1, 2] = H / 2
50
+ intrinsics[..., 2, 2] = 1.0
51
+
52
+ return extrinsics, intrinsics
legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/rotation.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ # Modified from PyTorch3D, https://github.com/facebookresearch/pytorch3d
8
+
9
+ import torch
10
+ import torch.nn.functional as F
11
+
12
+
13
+ def quat_to_mat(quaternions: torch.Tensor) -> torch.Tensor:
14
+ """
15
+ Quaternion Order: XYZW or say ijkr, scalar-last
16
+
17
+ Convert rotations given as quaternions to rotation matrices.
18
+ Args:
19
+ quaternions: quaternions with real part last,
20
+ as tensor of shape (..., 4).
21
+
22
+ Returns:
23
+ Rotation matrices as tensor of shape (..., 3, 3).
24
+ """
25
+ i, j, k, r = torch.unbind(quaternions, -1)
26
+ two_s = 2.0 / (quaternions * quaternions).sum(-1)
27
+
28
+ o = torch.stack(
29
+ (
30
+ 1 - two_s * (j * j + k * k),
31
+ two_s * (i * j - k * r),
32
+ two_s * (i * k + j * r),
33
+ two_s * (i * j + k * r),
34
+ 1 - two_s * (i * i + k * k),
35
+ two_s * (j * k - i * r),
36
+ two_s * (i * k - j * r),
37
+ two_s * (j * k + i * r),
38
+ 1 - two_s * (i * i + j * j),
39
+ ),
40
+ -1,
41
+ )
42
+ return o.reshape(quaternions.shape[:-1] + (3, 3))
43
+
44
+
45
+ def mat_to_quat(matrix: torch.Tensor) -> torch.Tensor:
46
+ """
47
+ Convert rotations given as rotation matrices to quaternions.
48
+
49
+ Args:
50
+ matrix: Rotation matrices as tensor of shape (..., 3, 3).
51
+
52
+ Returns:
53
+ quaternions with real part last, as tensor of shape (..., 4).
54
+ Quaternion Order: XYZW or say ijkr, scalar-last
55
+ """
56
+ if matrix.size(-1) != 3 or matrix.size(-2) != 3:
57
+ raise ValueError(f"Invalid rotation matrix shape {matrix.shape}.")
58
+
59
+ batch_dim = matrix.shape[:-2]
60
+ m00, m01, m02, m10, m11, m12, m20, m21, m22 = torch.unbind(matrix.reshape(batch_dim + (9,)), dim=-1)
61
+
62
+ q_abs = _sqrt_positive_part(
63
+ torch.stack(
64
+ [1.0 + m00 + m11 + m22, 1.0 + m00 - m11 - m22, 1.0 - m00 + m11 - m22, 1.0 - m00 - m11 + m22], dim=-1
65
+ )
66
+ )
67
+
68
+ # we produce the desired quaternion multiplied by each of r, i, j, k
69
+ quat_by_rijk = torch.stack(
70
+ [
71
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
72
+ # `int`.
73
+ torch.stack([q_abs[..., 0] ** 2, m21 - m12, m02 - m20, m10 - m01], dim=-1),
74
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
75
+ # `int`.
76
+ torch.stack([m21 - m12, q_abs[..., 1] ** 2, m10 + m01, m02 + m20], dim=-1),
77
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
78
+ # `int`.
79
+ torch.stack([m02 - m20, m10 + m01, q_abs[..., 2] ** 2, m12 + m21], dim=-1),
80
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
81
+ # `int`.
82
+ torch.stack([m10 - m01, m20 + m02, m21 + m12, q_abs[..., 3] ** 2], dim=-1),
83
+ ],
84
+ dim=-2,
85
+ )
86
+
87
+ # We floor here at 0.1 but the exact level is not important; if q_abs is small,
88
+ # the candidate won't be picked.
89
+ flr = torch.tensor(0.1).to(dtype=q_abs.dtype, device=q_abs.device)
90
+ quat_candidates = quat_by_rijk / (2.0 * q_abs[..., None].max(flr))
91
+
92
+ # if not for numerical problems, quat_candidates[i] should be same (up to a sign),
93
+ # forall i; we pick the best-conditioned one (with the largest denominator)
94
+ out = quat_candidates[F.one_hot(q_abs.argmax(dim=-1), num_classes=4) > 0.5, :].reshape(batch_dim + (4,))
95
+
96
+ # Convert from rijk to ijkr
97
+ out = out[..., [1, 2, 3, 0]]
98
+
99
+ out = standardize_quaternion(out)
100
+
101
+ return out
102
+
103
+
104
+ def _sqrt_positive_part(x: torch.Tensor) -> torch.Tensor:
105
+ """
106
+ Returns torch.sqrt(torch.max(0, x))
107
+ but with a zero subgradient where x is 0.
108
+ """
109
+ ret = torch.zeros_like(x)
110
+ positive_mask = x > 0
111
+ if torch.is_grad_enabled():
112
+ ret[positive_mask] = torch.sqrt(x[positive_mask])
113
+ else:
114
+ ret = torch.where(positive_mask, torch.sqrt(x), ret)
115
+ return ret
116
+
117
+
118
+ def standardize_quaternion(quaternions: torch.Tensor) -> torch.Tensor:
119
+ """
120
+ Convert a unit quaternion to a standard form: one in which the real
121
+ part is non negative.
122
+
123
+ Args:
124
+ quaternions: Quaternions with real part last,
125
+ as tensor of shape (..., 4).
126
+
127
+ Returns:
128
+ Standardized quaternions as tensor of shape (..., 4).
129
+ """
130
+ return torch.where(quaternions[..., 3:4] < 0, -quaternions, quaternions)
legacy/vggt_newbank_boxing_gloves_step19999/params/_METADATA ADDED
The diff for this file is too large to render. See raw diff
 
legacy/vggt_newbank_boxing_gloves_step19999/params/_sharding ADDED
The diff for this file is too large to render. See raw diff
 
legacy/vggt_newbank_boxing_gloves_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": [288], "chunk_shape": [288], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoder_norm.scale.value", "write_shape": [288], "chunk_shape": [288], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.LayerNorm_0.bias.value", "write_shape": [27, 288], "chunk_shape": [27, 288], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.LayerNorm_0.scale.value", "write_shape": [27, 288], "chunk_shape": [27, 288], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.LayerNorm_1.bias.value", "write_shape": [27, 288], "chunk_shape": [27, 288], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.LayerNorm_1.scale.value", "write_shape": [27, 288], "chunk_shape": [27, 288], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MlpBlock_0.Dense_0.bias.value", "write_shape": [27, 1076], "chunk_shape": [27, 1076], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MlpBlock_0.Dense_0.kernel.value", "write_shape": [27, 288, 4304], "chunk_shape": [27, 288, 4304], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MlpBlock_0.Dense_1.bias.value", "write_shape": [27, 288], "chunk_shape": [27, 288], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MlpBlock_0.Dense_1.kernel.value", "write_shape": [27, 1076, 1152], "chunk_shape": [27, 1076, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.key.bias.value", "write_shape": [27, 4, 72], "chunk_shape": [27, 4, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.key.kernel.value", "write_shape": [27, 288, 16, 72], "chunk_shape": [27, 288, 16, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.out.bias.value", "write_shape": [27, 288], "chunk_shape": [27, 288], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.out.kernel.value", "write_shape": [27, 4, 72, 1152], "chunk_shape": [27, 4, 72, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.query.bias.value", "write_shape": [27, 4, 72], "chunk_shape": [27, 4, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.query.kernel.value", "write_shape": [27, 288, 16, 72], "chunk_shape": [27, 288, 16, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.value.bias.value", "write_shape": [27, 4, 72], "chunk_shape": [27, 4, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.Transformer.encoderblock.MultiHeadDotProductAttention_0.value.kernel.value", "write_shape": [27, 288, 16, 72], "chunk_shape": [27, 288, 16, 72], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.embedding.bias.value", "write_shape": [288], "chunk_shape": [288], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.embedding.kernel.value", "write_shape": [14, 14, 3, 288], "chunk_shape": [14, 14, 3, 288], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.head.bias.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.head.kernel.value", "write_shape": [288, 2048], "chunk_shape": [288, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.img.pos_embedding.value", "write_shape": [1, 64, 1152], "chunk_shape": [1, 64, 1152], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.embedder.input_embedding.value", "write_shape": [64288, 2048], "chunk_shape": [64288, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.final_norm.scale.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.final_norm_1.Dense_0.bias.value", "write_shape": [768], "chunk_shape": [768], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.final_norm_1.Dense_0.kernel.value", "write_shape": [256, 3072], "chunk_shape": [256, 3072], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.attn_vec_einsum.w.value", "write_shape": [18, 2, 256, 2048], "chunk_shape": [18, 2, 256, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.attn_vec_einsum_1.w.value", "write_shape": [18, 2, 256, 1024], "chunk_shape": [18, 2, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.kv_einsum.w.value", "write_shape": [18, 2, 1, 512, 256], "chunk_shape": [18, 2, 1, 512, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.kv_einsum_1.w.value", "write_shape": [18, 2, 1, 256, 256], "chunk_shape": [18, 2, 1, 256, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.q_einsum.w.value", "write_shape": [18, 2, 2048, 256], "chunk_shape": [18, 2, 2048, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.attn.q_einsum_1.w.value", "write_shape": [18, 2, 1024, 256], "chunk_shape": [18, 2, 1024, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.mlp.gating_einsum.value", "write_shape": [18, 2, 512, 16384], "chunk_shape": [18, 2, 512, 16384], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.mlp.linear.value", "write_shape": [18, 4096, 2048], "chunk_shape": [18, 4096, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.mlp_1.gating_einsum.value", "write_shape": [18, 2, 256, 4096], "chunk_shape": [18, 2, 256, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.mlp_1.linear.value", "write_shape": [18, 1024, 1024], "chunk_shape": [18, 1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_attention_norm.scale.value", "write_shape": [18, 512], "chunk_shape": [18, 512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_attention_norm_1.Dense_0.bias.value", "write_shape": [18, 768], "chunk_shape": [18, 768], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_attention_norm_1.Dense_0.kernel.value", "write_shape": [18, 256, 3072], "chunk_shape": [18, 256, 3072], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_ffw_norm.scale.value", "write_shape": [18, 512], "chunk_shape": [18, 512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_ffw_norm_1.Dense_0.bias.value", "write_shape": [18, 768], "chunk_shape": [18, 768], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.pre_ffw_norm_1.Dense_0.kernel.value", "write_shape": [18, 256, 3072], "chunk_shape": [18, 256, 3072], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_branch.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_branch.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.k_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.k_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.k_rmsnorm.scale.value", "write_shape": [18, 32], "chunk_shape": [18, 32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.kv_norm.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.kv_norm.scale.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.log_gain.value", "write_shape": [18, 2], "chunk_shape": [18, 2], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.out_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.out_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.q_norm.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.q_norm.scale.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.q_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.q_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.q_rmsnorm.scale.value", "write_shape": [18, 32], "chunk_shape": [18, 32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.v_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.left_xattn.v_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.k_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.k_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.k_rmsnorm.scale.value", "write_shape": [18, 32], "chunk_shape": [18, 32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.kv_norm.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.kv_norm.scale.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.log_gain.value", "write_shape": [18, 2], "chunk_shape": [18, 2], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.out_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.out_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.q_norm.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.q_norm.scale.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.q_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.q_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.q_rmsnorm.scale.value", "write_shape": [18, 32], "chunk_shape": [18, 32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.v_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.main_xattn.v_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.merge_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.merge_proj.kernel.value", "write_shape": [18, 512, 1024], "chunk_shape": [18, 512, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_branch.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_branch.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.k_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.k_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.k_rmsnorm.scale.value", "write_shape": [18, 32], "chunk_shape": [18, 32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.kv_norm.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.kv_norm.scale.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.log_gain.value", "write_shape": [18, 2], "chunk_shape": [18, 2], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.out_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.out_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.q_norm.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.q_norm.scale.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.q_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.q_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.q_rmsnorm.scale.value", "write_shape": [18, 32], "chunk_shape": [18, 32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.v_proj.bias.value", "write_shape": [18, 256], "chunk_shape": [18, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.PaliGemma.llm.layers.spatial_inject_1.right_xattn.v_proj.kernel.value", "write_shape": [18, 256, 1024], "chunk_shape": [18, 256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.action_correlation_cholesky.value", "write_shape": [240, 960], "chunk_shape": [240, 960], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.action_in_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.action_in_proj.kernel.value", "write_shape": [8, 1024], "chunk_shape": [8, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.action_out_proj.bias.value", "write_shape": [8], "chunk_shape": [8], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.action_out_proj.kernel.value", "write_shape": [256, 32], "chunk_shape": [256, 32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fast_token_embedding.embedding.value", "write_shape": [256, 2048], "chunk_shape": [256, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fast_token_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fast_token_proj.kernel.value", "write_shape": [512, 1024], "chunk_shape": [512, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fusion_layer1.bias.value", "write_shape": [1024], "chunk_shape": [1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fusion_layer1.kernel.value", "write_shape": [1024, 4096], "chunk_shape": [1024, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fusion_layer2.bias.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.fusion_layer2.kernel.value", "write_shape": [1024, 2048], "chunk_shape": [1024, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_sincos.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_sincos.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_task.bias.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_task.kernel.value", "write_shape": [1024, 2048], "chunk_shape": [1024, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_task_stage.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.gate_task_stage.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.kv_transform.k_bias.value", "write_shape": [18, 1, 64], "chunk_shape": [18, 1, 64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.kv_transform.k_coeffs.value", "write_shape": [18, 18], "chunk_shape": [18, 18], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.kv_transform.v_bias.value", "write_shape": [18, 1, 64], "chunk_shape": [18, 1, 64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.kv_transform.v_coeffs.value", "write_shape": [18, 18], "chunk_shape": [18, 18], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.bank_token_embeds.left.value", "write_shape": [1, 24, 1024], "chunk_shape": [1, 24, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.bank_token_embeds.main.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.right.value", "write_shape": [1, 24, 1024], "chunk_shape": [1, 24, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_in_norm.bias.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_in_norm.scale.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_pose_mlp.fc1.bias.value", "write_shape": [64], "chunk_shape": [64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_pose_mlp.fc1.kernel.value", "write_shape": [3, 256], "chunk_shape": [3, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_pose_mlp.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_pose_mlp.fc2.kernel.value", "write_shape": [64, 1024], "chunk_shape": [64, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_token_proj.fc1.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_token_proj.fc1.kernel.value", "write_shape": [512, 1024], "chunk_shape": [512, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_token_proj.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_token_proj.fc2.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_token_proj.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cam_token_proj.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.conf_mlp.fc1.bias.value", "write_shape": [64], "chunk_shape": [64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.conf_mlp.fc1.kernel.value", "write_shape": [1, 64], "chunk_shape": [1, 64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.conf_mlp.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.conf_mlp.fc2.kernel.value", "write_shape": [64, 1024], "chunk_shape": [64, 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": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.k_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.k_proj.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.out_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.out_proj.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.q_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.q_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.q_proj.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.v_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.attn.v_proj.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.kv_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.kv_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.q_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.0.q_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.1.fc1.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.fc1.kernel.value", "write_shape": [256, 4096], "chunk_shape": [256, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.1.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.1.fc2.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.1.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.0.1.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.k_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.k_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.k_proj.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.1.0.attn.out_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.out_proj.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.1.0.attn.q_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.q_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.q_proj.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.1.0.attn.v_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.attn.v_proj.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.1.0.kv_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.kv_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.q_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.0.q_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.1.fc1.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.fc1.kernel.value", "write_shape": [256, 4096], "chunk_shape": [256, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.1.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.1.fc2.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.1.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.cross_view_fusion.blocks.1.1.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.depth_mlp.fc1.bias.value", "write_shape": [64], "chunk_shape": [64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.depth_mlp.fc1.kernel.value", "write_shape": [1, 64], "chunk_shape": [1, 64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.depth_mlp.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.depth_mlp.fc2.kernel.value", "write_shape": [64, 1024], "chunk_shape": [64, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.feat_in_norm.bias.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.feat_in_norm.scale.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.k_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.k_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.k_proj.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.out_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.out_proj.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.q_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.q_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.q_proj.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.v_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.attn.v_proj.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.kv_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.kv_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.q_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.0.q_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.1.fc1.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.fc1.kernel.value", "write_shape": [256, 4096], "chunk_shape": [256, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.1.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.1.fc2.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.1.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.0.1.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.k_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.k_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.k_proj.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.1.0.attn.out_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.out_proj.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.1.0.attn.q_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.q_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.q_proj.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.1.0.attn.v_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.attn.v_proj.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.1.0.kv_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.kv_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.q_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.0.q_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.1.fc1.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.fc1.kernel.value", "write_shape": [256, 4096], "chunk_shape": [256, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.1.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.1.fc2.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.1.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.left.layers.1.1.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.k_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.k_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.k_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.out_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.q_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.q_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.attn.v_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.kv_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.kv_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.q_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.0.q_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.1.fc1.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.fc1.kernel.value", "write_shape": [256, 4096], "chunk_shape": [256, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.1.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.1.fc2.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.1.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.0.1.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.k_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.k_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.k_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.out_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.q_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.q_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.attn.v_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.kv_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.kv_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.q_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.0.q_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.1.fc1.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.fc1.kernel.value", "write_shape": [256, 4096], "chunk_shape": [256, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.1.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.1.fc2.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.1.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.main.layers.1.1.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.k_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.k_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.k_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.out_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.q_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.q_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.attn.v_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.kv_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.kv_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.q_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.0.q_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.1.fc1.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.fc1.kernel.value", "write_shape": [256, 4096], "chunk_shape": [256, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.1.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.1.fc2.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.1.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.0.1.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.k_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.k_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.k_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.out_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.q_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.q_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 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": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.attn.v_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.kv_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.kv_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.q_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.0.q_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.1.fc1.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.fc1.kernel.value", "write_shape": [256, 4096], "chunk_shape": [256, 4096], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.1.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.1.fc2.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.1.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.lang_fusers.right.layers.1.1.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_embed.value", "write_shape": [1, 1024], "chunk_shape": [1, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_fuse.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_fuse.kernel.value", "write_shape": [1024, 1024], "chunk_shape": [1024, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.fc1.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.fc1.kernel.value", "write_shape": [512, 1024], "chunk_shape": [512, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.fc2.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.0.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.fc1.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.fc1.kernel.value", "write_shape": [512, 1024], "chunk_shape": [512, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.fc2.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.1.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.fc1.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.fc1.kernel.value", "write_shape": [512, 1024], "chunk_shape": [512, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.fc2.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.2.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.fc1.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.fc1.kernel.value", "write_shape": [512, 1024], "chunk_shape": [512, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.fc2.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.layer_projectors.3.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.loc_log_gamma.value", "write_shape": [2], "chunk_shape": [2], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.fc1.bias.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.fc1.kernel.value", "write_shape": [256, 2048], "chunk_shape": [256, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.fc2.kernel.value", "write_shape": [512, 1024], "chunk_shape": [512, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.mlp.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.out_ln.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.out_ln.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.query.value", "write_shape": [1, 24, 1024], "chunk_shape": [1, 24, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.k_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.k_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.k_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.log_gain.value", "write_shape": [2], "chunk_shape": [2], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.out_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.out_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.q_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.q_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.q_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.v_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.attn.v_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.kv_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.kv_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.q_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.left.xattn.q_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.loc_log_gamma.value", "write_shape": [2], "chunk_shape": [2], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.fc1.bias.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.fc1.kernel.value", "write_shape": [256, 2048], "chunk_shape": [256, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.fc2.kernel.value", "write_shape": [512, 1024], "chunk_shape": [512, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.mlp.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.out_ln.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.out_ln.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.query.value", "write_shape": [1, 32, 1024], "chunk_shape": [1, 32, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.k_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.k_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.k_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.log_gain.value", "write_shape": [2], "chunk_shape": [2], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.out_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.out_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.q_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.q_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.q_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.v_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.attn.v_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.kv_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.kv_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.q_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.main.xattn.q_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.loc_log_gamma.value", "write_shape": [2], "chunk_shape": [2], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.fc1.bias.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.fc1.kernel.value", "write_shape": [256, 2048], "chunk_shape": [256, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.fc2.kernel.value", "write_shape": [512, 1024], "chunk_shape": [512, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.mlp.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.out_ln.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.out_ln.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.query.value", "write_shape": [1, 24, 1024], "chunk_shape": [1, 24, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.k_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.k_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.k_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.log_gain.value", "write_shape": [2], "chunk_shape": [2], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.out_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.out_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.q_ln.scale.value", "write_shape": [32], "chunk_shape": [32], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.q_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.q_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.v_proj.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.attn.v_proj.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.kv_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.kv_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.q_norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.perceivers.right.xattn.q_norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pos2d_mlp.fc1.bias.value", "write_shape": [64], "chunk_shape": [64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pos2d_mlp.fc1.kernel.value", "write_shape": [2, 64], "chunk_shape": [2, 64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pos2d_mlp.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pos2d_mlp.fc2.kernel.value", "write_shape": [64, 1024], "chunk_shape": [64, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pose_enc_mlp.fc1.bias.value", "write_shape": [64], "chunk_shape": [64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pose_enc_mlp.fc1.kernel.value", "write_shape": [9, 64], "chunk_shape": [9, 64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pose_enc_mlp.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.pose_enc_mlp.fc2.kernel.value", "write_shape": [64, 1024], "chunk_shape": [64, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.ray_mlp.fc1.bias.value", "write_shape": [64], "chunk_shape": [64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.ray_mlp.fc1.kernel.value", "write_shape": [6, 64], "chunk_shape": [6, 64], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.ray_mlp.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.ray_mlp.fc2.kernel.value", "write_shape": [64, 1024], "chunk_shape": [64, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.fc1.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.fc1.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.fc2.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.fc2.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.norm.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.t5_projector.norm.scale.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.spatial_bank_builder.view_embed.embedding.value", "write_shape": [3, 256], "chunk_shape": [3, 256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.stage_pred_from_vlm.bias.value", "write_shape": [15], "chunk_shape": [15], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.stage_pred_from_vlm.kernel.value", "write_shape": [512, 15], "chunk_shape": [512, 15], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.stage_projection.bias.value", "write_shape": [512], "chunk_shape": [512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.stage_projection.kernel.value", "write_shape": [512, 2048], "chunk_shape": [512, 2048], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.task_embeddings.embedding.value", "write_shape": [50, 512], "chunk_shape": [50, 512], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.task_stage_embeddings.embedding.value", "write_shape": [149, 1024], "chunk_shape": [149, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.time_mlp_in.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.time_mlp_in.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.time_mlp_out.bias.value", "write_shape": [256], "chunk_shape": [256], "ext_metadata": null}}, {"array_metadata": {"param_name": "params.time_mlp_out.kernel.value", "write_shape": [256, 1024], "chunk_shape": [256, 1024], "ext_metadata": null}}]}
legacy/vggt_newbank_boxing_gloves_step19999/params/d/a46900611b6ae8d52cc9c73344a60cd5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f9f44289129b1bc2cde3993756c5441b0797f8f35aa3ac2307242ef4e1911c25
3
+ size 639734
legacy/vggt_newbank_boxing_gloves_step19999/params/manifest.ocdbt ADDED
Binary file (120 Bytes). View file
 
legacy/vggt_newbank_boxing_gloves_step19999/params/ocdbt.process_0/d/04eb8ca89e0a583a9990df03b6a7b2a1 ADDED
Binary file (2.5 kB). View file