JackLiu0406's picture
Fix stale line in task-77 card
9d00298 verified
|
Raw
History Blame Contribute Delete
9.6 kB
# Task 77 — `installing_a_modem` (PiBehavior / pi0.5, no DA3)
Single-task fine-tune for the [BEHAVIOR-1K](https://behavior.stanford.edu/) 2026 challenge.
Part of [`JackLiu0406/b1k-checkpoints`](https://huggingface.co/JackLiu0406/b1k-checkpoints).
This is one checkpoint line in the repo; the DA3 K/V-split spatial models are documented in the root README.
---
## Quick facts
| | |
|---|---|
| architecture | PiBehavior / π0.5 — `gemma_2b` VLM + `gemma_300m` action expert |
| parameters | 3,826.8 M |
| framework | JAX / Flax NNX, Orbax checkpoint |
| initialised from | [`IliaLarchenko/behavior_50t_checkpoint`](https://huggingface.co/IliaLarchenko/behavior_50t_checkpoint) (50-task meta-trained) |
| fine-tuned on | task **77** = `installing_a_modem`, 200 episodes, ~476 k samples |
| checkpoint step | **29,000** (of a 30,000-step schedule — see *Caveats*) |
| task-embedding space | **100 tasks** (expanded from the upstream 50) |
| DA3 | **not used** — this is the plain π0.5 path |
| precision | params stored as saved by Orbax; model runs bf16 compute |
### Final training metrics (step 29,275, last logged)
| metric | value |
|---|---|
| `action_loss` | 0.0081 |
| `total_loss` | 0.0097 |
| `fast_accuracy` | 0.9932 |
| `subtask_accuracy` | 1.0000 |
---
## Repo layout
```
task77_installing_a_modem/
└── 29000/
├── params/ # Orbax OCDBT param tree (~11.8 GiB) — inference weights
├── assets/
│ └── IliaLarchenko/
│ └── behavior_224_rgb/
│ ├── norm_stats.json # state/action normalisation
│ └── fast_tokenizer/ # FAST action tokenizer (auxiliary head)
└── _CHECKPOINT_METADATA
```
> **`train_state/` is deliberately not included.** That directory is 31 GB of Adam
> optimiser moments, needed only to *resume training*. Everything required for
> inference and evaluation is in `params/`.
---
## Input contract
This is the part most people get wrong, so it is spelled out exactly.
### ⚠️ There is no text prompt
PiBehavior does **not** take a language instruction. It is conditioned by a
**task embedding** and a **stage (subtask) embedding**, both looked up by integer
index. Any `prompt` string you pass is ignored — `serve_b1k.py` sets a placeholder
string purely for logging.
### Observation dict
| key | shape | dtype | notes |
|---|---|---|---|
| `images["base_0_rgb"]` | `[B, 224, 224, 3]` | `float32` | head/base camera |
| `images["left_wrist_0_rgb"]` | `[B, 224, 224, 3]` | `float32` | left wrist camera |
| `images["right_wrist_0_rgb"]` | `[B, 224, 224, 3]` | `float32` | right wrist camera |
| `image_masks[<same three keys>]` | `[B]` | `bool` | **one flag per image, not per pixel** — `False` marks a missing camera |
| `state` | `[B, 32]` | `float32` | proprioception, normalised with `norm_stats.json` |
| `tokenized_prompt` | `[B, 2]` | `int32` | **`[task_id, subtask_state]`** |
| `tokenized_prompt_mask` | `[B, 2]` | `bool` | normally `[True, True]` |
`fast_tokens` / `fast_token_mask` appear only when the FAST auxiliary loss is
enabled; they are a **training-time** auxiliary target and are not needed at
inference.
Image resolution is `openpi.models.model.IMAGE_RESOLUTION == (224, 224)`.
### Output
| key | shape | dtype |
|---|---|---|
| `actions` | `[B, 30, 32]` | `float32` |
That is an **action chunk**: horizon 30, action dimension 32. Denormalise with the
`actions` entry of `norm_stats.json`. The 32 dims cover base velocity (x, y, z),
left arm (7) + left gripper, right arm (7) + right gripper, and trunk (4), with the
remainder padding to 32.
---
## Task and stage indexing — important
The upstream release has a **50-task** embedding table. `installing_a_modem` is a
2026-only activity and is **not** in those 50, so this model was trained with the
table expanded to **100 tasks**; rows 50–99 were freshly initialised from
`normal(0, 1/√features)` and rows 0–49 were carried over unchanged.
**You must load this checkpoint with a 100-task config**, e.g. `B1K_TASK_SPACE=100`.
Loading it into a 50-task model will fail on a shape mismatch.
Stage counts follow the verified upstream rule:
```python
num_stages = clip(ceil(avg_episode_length / 900), 5, 15)
```
For this model:
| | value |
|---|---|
| `task_id` | **77** |
| `num_stages` | **5** |
| stage-embedding rows | **888 – 892** |
`subtask_state` at inference is the current stage index in `0 … num_stages-1`
(so `0–4` here). The absolute embedding row is
`TASK_STAGE_OFFSETS[task_id] + subtask_state`; the offsets are a cumulative sum
over `TASK_NUM_STAGES` (100 entries, summing to 1120).
---
## Running it
The evaluation path is a **websocket policy server** that the OmniGibson
BEHAVIOR-1K evaluator connects to.
### 1. Get the code
```bash
git clone https://github.com/IliaLarchenko/behavior-1k-solution
cd behavior-1k-solution
git submodule update --init --recursive
uv sync
```
> This checkpoint was produced by a fork of the above with the task table widened
> to 100. If you use stock upstream, you must apply the same widening or the
> parameter shapes will not match.
### 2. Download the checkpoint
```bash
huggingface-cli download <REPO_ID> \
--include "task77_installing_a_modem/29000/*" \
--local-dir ./ckpts
```
### 3. Serve the policy
```bash
export B1K_TASK_SPACE=100 # 100-task embedding table
export XLA_PYTHON_CLIENT_MEM_FRACTION=0.9
uv run scripts/serve_b1k.py \
--policy.config pi_behavior_b1k_fast \
--policy.dir ./ckpts/task77_installing_a_modem/29000 \
--task_id 77 \
--port 8000
```
`--task_id 77` is what selects the task embedding. Omit it only if your
observations already carry a `task_index` / `task_id` field, in which case it is
read per-observation.
`assets_base_dir` must resolve so that `norm_stats.json` and `fast_tokenizer/` are
found under `<assets>/IliaLarchenko/behavior_224_rgb/`. The bundled `assets/`
directory already has that layout — point the config at it if the default path
does not exist on your machine.
### Eval-side execution parameters
`serve_b1k.py` wraps the raw policy in `B1KPolicyWrapper`, which does chunk
scheduling and a few eval heuristics. Defaults:
| arg | default | meaning |
|---|---|---|
| `--actions_to_execute` | 26 | actions consumed per inference |
| `--actions_to_keep` | 4 | overlap retained between chunks |
| `--execute_in_n_steps` | 20 | env steps per executed chunk |
| `--history_len` | 3 | observation history for stage voting |
| `--votes_to_promote` | 2 | consecutive votes before advancing a stage |
| `--num_steps` | 20 | flow-matching sampling steps |
| `--apply_eval_tricks` | `True` | correction rules + gripper variation checks |
Set `--apply_eval_tricks False` for a clean measurement of the policy itself.
### Loading programmatically
```python
from b1k.policies import policy_config as _policy_config
from b1k.training import config as _config
policy = _policy_config.create_trained_policy(
_config.get_config("pi_behavior_b1k_fast"),
"./ckpts/task77_installing_a_modem/29000",
sample_kwargs={"num_steps": 20},
)
action_chunk = policy.infer({
"images": {...}, # three [224,224,3] float32 arrays
"image_masks": {...}, # three bools
"state": state_32, # [32] float32
"task_id": 77,
"subtask_state": current_stage, # 0..4
})["actions"] # [30, 32]
```
---
## Training recipe
| | |
|---|---|
| init | `IliaLarchenko/behavior_50t_checkpoint` params, task/stage tables widened 50→100 |
| GPUs | 7 × H200 (one card on the node was faulty and excluded) |
| global batch | 224 (32 per GPU) |
| FSDP | off — parameters replicated |
| steps | 30,000 scheduled; **stopped at 29,275, last checkpoint 29,000** |
| LR schedule | ramp `8.75e-7` → peak `8.75e-5` over 2,000 warmup steps, cosine to `1.75e-6` |
| LR scaling | linear rule (Goyal et al.), 1.75× over a BS-128 reference of `5e-5` |
| optimiser | AdamW (openpi defaults) |
| new params | `task_embeddings[77]` and `task_stage_embeddings[888:893]` initialised from `normal(0, 1/√features)`; everything else transferred |
The FAST action tokenizer was **not** retrained. It was verified to transfer to the
new tasks: 0.000 % alphabet overflow, no clipping, and round-trip MAE on the new
task matching in-distribution controls.
---
## Caveats — read before comparing numbers
1. **This is step 29,000, not 30,000.** Training was stopped 725 steps early to free
the machine for hardware fault diagnosis. Those steps were at the LR floor
(~1.75e-6) and loss was already flat, so the practical difference is very small —
but it is not the full schedule.
2. **Inference only.** `train_state/` is not included, so you cannot resume training
from this upload.
3. **Base-velocity coordinate frame.** Trained on the 2026 challenge demos *after*
the upstream fix (commit `e6c9756`) that moved `base_qvel` from the world frame to
the robot frame. Checkpoints trained on the older world-frame data saw a different
input distribution for state dims 0:3 — losses are not directly comparable across
that boundary.
4. **Single-task model.** It has only been fine-tuned on task 77. The other 99 task
embeddings are either inherited from the 50-task meta checkpoint (0–49) or still
at random initialisation (50–99, except 77). Do not expect meaningful behaviour
for any other `task_id`.
5. **`subtask_accuracy = 1.0000`** is on *training* data — it is not a generalisation
estimate.