legacy: copy vggt_newbank_boxing_gloves_step19999 -> legacy/vggt_newbank_boxing_gloves_step19999
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +13 -0
- legacy/vggt_newbank_boxing_gloves_step19999/README.md +100 -0
- legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/metadata.json +25 -0
- legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/processing_action_tokenizer.py +158 -0
- legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/processor_config.json +11 -0
- legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/special_tokens_map.json +1 -0
- legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/tokenizer.json +0 -0
- legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/fast_tokenizer/tokenizer_config.json +11 -0
- legacy/vggt_newbank_boxing_gloves_step19999/assets/IliaLarchenko/behavior_224_rgb/norm_stats.json +3 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/reference/eval_b1k_wrapper.py +307 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/reference/policy_config.py +119 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/reference/serve_b1k.py +190 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/scripts/train_2026.py +180 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/models/observation.py +176 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/models/pi_behavior.py +1327 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/models/pi_behavior_config.py +291 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/models/spatial_da3.py +593 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/training/b1k_2026.py +414 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/training/b1k_da3.py +321 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/src/b1k/training/vggt_extractor.py +208 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/__init__.py +13 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/__init__.py +9 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/aggregator.py +250 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/__init__.py +11 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/camera_head.py +80 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/dense_head.py +308 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/text_alignment_head.py +79 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/heads/utils.py +108 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/__init__.py +27 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/attention.py +184 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/block.py +274 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/ffn_layers.py +83 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/layer_scale.py +35 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/patch_embed.py +95 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/rms_norm.py +30 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/rope_position_encoding.py +127 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/utils.py +136 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/layers/vision_transformer.py +424 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/models/vggt_omega.py +88 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/__init__.py +7 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/geometry.py +34 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/load_fn.py +129 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/pose_enc.py +52 -0
- legacy/vggt_newbank_boxing_gloves_step19999/code/third_party/vggt_omega/utils/rotation.py +130 -0
- legacy/vggt_newbank_boxing_gloves_step19999/params/_METADATA +0 -0
- legacy/vggt_newbank_boxing_gloves_step19999/params/_sharding +0 -0
- legacy/vggt_newbank_boxing_gloves_step19999/params/array_metadatas/process_0 +1 -0
- legacy/vggt_newbank_boxing_gloves_step19999/params/d/a46900611b6ae8d52cc9c73344a60cd5 +3 -0
- legacy/vggt_newbank_boxing_gloves_step19999/params/manifest.ocdbt +0 -0
- 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
|
|
|