# Pi0.5 (π0.5) VLA > Vision-Language-Action policy serving over the OpenPI realtime websocket ## Summary - Vendor: Physical Intelligence (checkpoint redistributed by LeRobot) - Model: `lerobot/pi05_base` - Task: Vision-Language-Action — multi-camera images + a language instruction + robot proprioceptive state → a continuous action chunk `[action_horizon, action_dim] = [50, 32]` via flow-matching denoising. π0.5 does **not** emit text tokens. - Mode: Online serving via the OpenPI realtime robot API (`/v1/realtime/robot/openpi` websocket) - Maintainer: Community ## When to use this recipe Use this recipe to deploy `lerobot/pi05_base` as a robot policy server: a robot client connects over the OpenPI websocket and, per control step, sends an observation (camera frames + state + instruction) and receives an action chunk. A single pipeline class (`Pi05Pipeline`) serves the policy. π0.5 is stateless across calls (first-order Markov, no KV reuse), so the protocol's `session_id` / `reset` are accepted but ignored. This recipe covers the functional path only. Serving runs in eager mode, with no CUDA Graph capture, no Triton kernels and no prefix or encoder caching, and both `float32` and `bfloat16` are supported. Two π0.5 capabilities are not implemented, and a checkpoint that declares either is rejected when it loads: - **MEM** short-horizon observation memory (`use_visual_memory`, `use_proprioceptive_memory`) - **RTC** real-time chunking (`rtc_config`) A checkpoint with `use_relative_actions` must also carry `action_feature_names` in `config.json`. ## Weights | Component | Params | Part of | | --- | ---: | --- | | Vision tower, SigLIP So400m/14 | 412 M | PaliGemma | | Language model, Gemma-2B, with the multimodal projector | 2.51 B | PaliGemma | | Action expert, Gemma-300M, with AdaRMS and the action/time heads | 693 M | π0.5 | | Total | 3.62 B | 13.5 GiB in float32 | | Artifact | Repository | | --- | --- | | `model.safetensors`, 812 tensors, ~14.5 GB | `lerobot/pi05_base` | | `policy_preprocessor.json` + `policy_preprocessor_step_*.safetensors` | `lerobot/pi05_base` | | Tokenizer, gated | `google/paligemma-3b-pt-224` | ## Hardware Support ### 1x H100 80GB (Online serving) The nightly end-to-end job runs on one H100, and serving was also verified on an RTX 4090 and an RTX PRO 5000. #### Environment - OS: Ubuntu 22.04+ - Python: 3.12+ - Driver / runtime: NVIDIA CUDA environment - vLLM / vLLM-Omni: use the commit you are deploying from - Hugging Face: `google/paligemma-3b-pt-224` license accepted, `hf auth login` - OpenPI client extras (robot side, and for the e2e test): `openpi-client`, `websockets`, `msgpack`, `msgpack-numpy`. CI pins `openpi-client` to OpenPI revision `215abfb217dbac7d5f1273282331b9b1866c0479`. #### Command ```bash vllm serve lerobot/pi05_base \ --omni \ --host 0.0.0.0 --port 8000 \ --deploy-config vllm_omni/deploy/pi05.yaml \ --enforce-eager --disable-log-stats ``` The deploy config (`vllm_omni/deploy/pi05.yaml`) declares a single diffusion stage (`Pi05Pipeline`), `float32`, `max_num_seqs: 1`, `tokenizer_max_length: 200` and `state_num_bins: 256`, together with the `policy_server_config` handshake metadata (3 cameras, `joint_position`, action horizon 50, action dim 32) that enables the OpenPI realtime serving layer. Keep `action_horizon`, `action_dim` and `image_resolution` in that block in sync with `model_config`. To serve in bfloat16, set `dtype: bfloat16` in the same file. #### Verification The observation is a flat dict per inference: ```python { "observation.images.base_0_rgb": np.uint8[H, W, 3], # or float32 in [0, 1] "observation.images.left_wrist_0_rgb": np.uint8[H, W, 3], "observation.images.right_wrist_0_rgb": np.uint8[H, W, 3], "state": np.float32[state_dim], # must match the checkpoint's declared width "prompt": "pick up the red block", "session_id": "", # accepted but ignored (π0.5 is stateless) "sampling_params": {"num_inference_steps": 4}, # optional engine knobs } ``` Camera keys must match the server's `image_feature_keys`, which is the checkpoint's `input_features` order. If your robot uses different camera names, set `model_config.image_key_map` in `pi05.yaml` to map raw obs keys → feature keys. Every request needs at least one configured camera and a non-null state. `state` must have the width `input_features["observation.state"]` declares, 32 for `lerobot/pi05_base`. That checkpoint ships no normalization statistics, so its values must already be normalized. PIL and uint8 images use the `[0, 255]` domain, while floating ndarray and tensor images must be finite and in `[0, 1]`. Both are converted with `x * 2 - 1`. Engine knobs live under `sampling_params`. It currently holds `num_inference_steps`, which overrides the deploy-config denoising steps for a single request. ### Target-hardware validation Client E2E round trip over the OpenPI websocket: msgpack pack, send, engine, denoise, unpack. Eager, batch 1, 10 denoising steps, p50 of 30 round trips after 3 warmup. | GPU | dtype | Client E2E p50 (ms), 1 / 2 / 3 cameras | Device peak (GiB) | | --- | --- | ---: | ---: | | H100 80 GB HBM3 (sm_90) | float32 | 282 / 356 / 398 | 15.0 | | H100 80 GB HBM3 (sm_90) | bfloat16 | 290 / 301 / 312 | 9.1 | | RTX PRO 5000 72 GB (sm_120) | float32 | 185 / 231 / 281 | 14.8 | | RTX PRO 5000 72 GB (sm_120) | bfloat16 | 156 / 177 / 203 | 8.9 | ## Correctness π0.5's flow-matching kernel reproduces the LeRobot `PI05Policy` reference exactly (`max|Δ| = 0`, CPU/float32, fixed noise, 1/2/3 cameras). The same file also compares the prompt tokens carrying the discretized state, the relative-action path, and a bfloat16 run against LeRobot in bfloat16: `tests/diffusion/models/pi05/test_pi05_parity.py`. ## References - Model blog: - LeRobot π0.5 docs: - Checkpoint: - Pipeline: [`vllm_omni/diffusion/models/pi05/pipeline_pi05.py`](../../vllm_omni/diffusion/models/pi05/pipeline_pi05.py) - Deploy config: [`vllm_omni/deploy/pi05.yaml`](../../vllm_omni/deploy/pi05.yaml) - Tests: CPU units + LeRobot parity in [`tests/diffusion/models/pi05/`](../../tests/diffusion/models/pi05/), OpenPI websocket e2e in [`tests/e2e/online_serving/test_pi05_expansion.py`](../../tests/e2e/online_serving/test_pi05_expansion.py)