| # OpenPI-COMET Backbone Modifications |
|
|
| This file records local changes made to `b1k/openpi-comet/` for integrating the |
| trained A2C2 correction head into BEHAVIOR online evaluation. |
|
|
| ## 2026-05-29: expose COMET prefix latent from action sampling |
|
|
| ### Files changed |
|
|
| - `b1k/openpi-comet/src/openpi/models/pi0.py` |
| - `b1k/openpi-comet/src/openpi/policies/policy.py` |
|
|
| ### `Pi0.sample_actions` |
| |
| Added an optional keyword argument: |
| |
| ```python |
| return_prefix_z: bool = False |
| ``` |
| |
| Default behavior is unchanged. With `return_prefix_z=False`, the function still |
| returns only the sampled action chunk: |
| |
| ```python |
| actions |
| ``` |
| |
| With `return_prefix_z=True`, the function returns: |
| |
| ```python |
| actions, prefix_z |
| ``` |
| |
| `prefix_z` is computed from the same prefix forward pass already used to build |
| the KV cache for action generation: |
| |
| 1. Run `embed_prefix(observation)`. |
| 2. Run `PaliGemma.llm([prefix_tokens, None], ...)`. |
| 3. Keep `prefix_out`. |
| 4. Apply `prefix_mask` weighted mean pooling. |
| |
| This matches the latent definition used by `b1k/a2c2_create_dataset.py`: |
| |
| ```text |
| mask-pooled prefix_out from COMET/OpenPI PI0.5 PaliGemma prefix forward |
| over image, prompt, and discrete state tokens |
| ``` |
| |
| Expected `prefix_z` shape for the current PI0.5 COMET checkpoint is: |
|
|
| ```text |
| [batch, 2048] |
| ``` |
|
|
| ### `Policy` JAX jit setup |
|
|
| Updated `openpi.policies.policy.Policy.__init__` so JAX policies detect whether |
| `model.sample_actions` supports `return_prefix_z`. If it does, the jitted wrapper |
| is created with: |
|
|
| ```python |
| static_argnames=("return_prefix_z",) |
| ``` |
|
|
| This keeps the Python branch static under JAX JIT and allows future A2C2 wrapper |
| code to call `sample_actions(..., return_prefix_z=True)` safely. |
|
|
| ### `Policy.infer_with_prefix_z` |
| |
| Added a new method: |
| |
| ```python |
| Policy.infer_with_prefix_z(obs, *, noise=None) -> dict |
| ``` |
| |
| This method follows the same preprocessing, input transforms, sampling, output |
| transforms, and timing pattern as `Policy.infer()`, but calls: |
| |
| ```python |
| self._sample_actions(..., return_prefix_z=True) |
| ``` |
| |
| and unpacks: |
| |
| ```python |
| actions, prefix_z |
| ``` |
| |
| The returned dictionary contains the normal transformed policy output plus the |
| untransformed COMET prefix latent: |
| |
| ```python |
| { |
| "actions": ..., # normal output-transformed action chunk |
| "prefix_z": ..., # float32-like numpy array, shape [2048] |
| "policy_timing": ..., |
| } |
| ``` |
| |
| `prefix_z` is appended after output transforms so task-specific transforms such |
| as `B1kOutputs` cannot drop or reshape it. |
| |
| This method currently supports the JAX / Orbax OpenPI-COMET path only. It raises |
| `NotImplementedError` for PyTorch policies or models whose `sample_actions` |
| does not expose `return_prefix_z`. |
| |
| ### Current behavior |
| |
| `Policy.infer()` behavior was not changed. Existing OpenPI-COMET baseline |
| serving still returns the same output as before: |
| |
| ```python |
| { |
| "state": ..., |
| "actions": ..., |
| "policy_timing": ..., |
| } |
| ``` |
| |
| The new latent return path is available through `Policy.infer_with_prefix_z()` |
| for a future A2C2-specific wrapper. |
| |
| ## 2026-05-30: add A2C2 online B1K wrapper |
| |
| ### Files changed |
| |
| - `b1k/openpi-comet/src/openpi/shared/a2c2_b1k_wrapper.py` |
| - `b1k/openpi-comet/scripts/serve_b1k_a2c2.py` |
| - `b1k/openpi-comet/src/a2c2/model.py` |
| - `b1k/openpi-comet/src/a2c2/dataset.py` |
| |
| ### `A2C2B1KPolicyWrapper` |
| |
| Added a dedicated wrapper that keeps the original `B1KPolicyWrapper` baseline |
| untouched. The first version supports only: |
| |
| ```text |
| control_mode = "receeding_horizon" |
| ``` |
| |
| The wrapper now imports the A2C2 model directly from the bundled OpenPI-COMET |
| source tree: |
| |
| ```python |
| from a2c2.model import A2C2CorrectionHead, A2C2CorrectionHeadConfig |
| ``` |
| |
| It no longer inserts the external `b1k/a2c2/src` directory into `sys.path`. |
| `a2c2_root` is kept as a deprecated CLI compatibility argument but is ignored. |
| |
| This is intentional because the A2C2 training tuples are: |
| |
| ```text |
| o_{t+k}, base_chunk_t[k], base_chunk_t, z_t, k -> delta_{t+k} |
| ``` |
| |
| So online execution should not correct the full future chunk at replan time. |
| Instead, the wrapper: |
| |
| 1. Replans with `policy.infer_with_prefix_z(batch)` when its queue is empty. |
| 2. Stores base chunk context plus `prefix_z` in the queue. |
| 3. At every environment step, uses the current 256-d BEHAVIOR proprio state to |
| predict the residual for the queued base action. |
| 4. Executes: |
| |
| ```python |
| final_action = base_action + residual_scale * a2c2_delta |
| ``` |
| |
| Optional clipping is available through `delta_clip`. |
|
|
| The queued context for each action is: |
|
|
| ```python |
| { |
| "base_action": base_chunk[k], |
| "base_action_chunk": base_chunk, |
| "base_policy_z": prefix_z, |
| "valid_action_mask": valid_mask, |
| "chunk_index": k, |
| } |
| ``` |
|
|
| ### `serve_b1k_a2c2.py` |
|
|
| Added a server entrypoint mirroring `scripts/serve_b1k.py`, but wrapping the |
| OpenPI-COMET policy with `A2C2B1KPolicyWrapper`. |
|
|
| Example: |
|
|
| ```bash |
| uv run --no-sync scripts/serve_b1k_a2c2.py \ |
| --task_name=tidying_bedroom \ |
| --control_mode=receeding_horizon \ |
| --max_len=32 \ |
| --port=8001 \ |
| --a2c2_checkpoint=/a2c2/results/latest.pt \ |
| --a2c2_device=cuda \ |
| policy:checkpoint \ |
| --policy.config=pi05_b1k-base \ |
| --policy.dir=/20TB_02/dennis_openpi/b1k/openpi-comet/checkpoints/pi05-b1kpt12-cs32 |
| ``` |
|
|
| This server still speaks the same BEHAVIOR websocket protocol as the baseline |
| server. The BEHAVIOR evaluation command can keep using `policy=websocket`. |
|
|