Skip to main content

Overview

pi0 turns camera views, task text, and robot state into a chunk of future actions. Its ws1 path keeps the entire inference path on one GPU. The vision and language stacks build a reusable prefix; conditioned on that prefix and the robot state, the action expert turns noise into an action chunk through flow matching. PI0WS1Scheduler implements only this single-GPU path. It has no tensor parallelism, continuous batching, or preemption.
Robot state enters pi0 as a numeric token on the expert side. In pi0.5, discretized state bins become part of the language prompt instead.

Architecture

The request enters PhyAI through its . PI0Entry builds the model and scheduler, while three runners own the vision, prefix, and expert work. Their implementation lives in these files:
phyai/src/phyai/models/pi0
main_pi0.py
scheduler_ws1_pi0.py
model_runner_pi0.py
modeling_pi0.py
configuration_pi0.py

Model layout

The code spans several modules, but the data path stays linear. SigLIP turns camera images into tokens, PaliGemma combines them with the task text, and the smaller Gemma expert produces the action trajectory. These stacks share a small set of top-level geometry settings: params_dtype controls the language and expert stacks. The vision tower keeps its own vision_params_dtype and defaults to fp32 to match the reference implementation. Pass PI0Args(vision_params_dtype=torch.bfloat16) only when bf16 vision is part of the experiment.

Request contract

At runtime, PI0Request carries the tensors that connect preprocessing to the scheduler: B may range from 1 to max_batch_size. Smaller batches are padded to the captured shape inside the scheduler, then sliced back to actual_B before the result is returned.

Scheduler phases

Once the request enters engine.step, the scheduler moves through these phases in order: The prefix remains unchanged during denoising, so the scheduler caches it once. State and action queries then see different portions of the cache:
The suffix therefore contains 1 + chunk_size tokens: one state token followed by the action chunk.

CUDA graphs

The fixed request geometry makes CUDA graph capture possible. With RuntimeConfig(use_cuda_graph=True), each runner captures its graph during scheduler.setup(): For each request, the runners refresh their static input buffers and replay the captured graphs. Attention metadata stays outside the captured region and is written through the backend’s capture-aware buffers.
Disable CUDA graphs when you need an expanded Nsight Systems trace:

Running pi0

1

Prepare weights

Use a HF-style pi0 PyTorch checkpoint directory containing config.json and model.safetensors. Omit --checkpoint only for a random-weight smoke test.
2

Construct the engine

Select the "pi0" plugin. Engine construction loads the optional checkpoint, prepares the runners, and captures CUDA graphs when enabled.
max_batch_size becomes part of the captured shape. Changing it requires a new engine.
3

Build a request

PI0Processor, provided by phyai-utils-tools, converts raw robot observations into the tensors expected by PI0Request.
4

Run one step

When the processor has an action_dim, processor.postprocess(actions) removes the padded action width and applies dataset unnormalization when statistics are available.
5

Close the engine

End-to-end example

examples/pi0/run_pi0.py can drive either a processed request or the raw smoke path. A checkpoint-backed run looks like this:
To check only the execution path, omit the checkpoint and use random weights:
A checkpoint with one empty camera expects two image streams:

Benchmarking and profiling

Use benchmark/bench_n_batch_ws1_pi0.py to compare batch sizes under the same scheduler configuration:
The same script can bracket a short Nsight Systems capture:
The default leaves the vision tower in fp32. Add --vision-dtype bfloat16 only when the experiment calls for bf16 vision timing.

Current limitations

Graph reuse depends on fixed geometry. PI0WS1Scheduler fixes max_batch_size when the single-GPU engine is built, while CUDA graph capture also fixes the camera count, image size, and tokenizer length. Changing any of them requires a new engine. The vision tower replays once for each real batch item rather than batching those items together. The scheduler begins after preprocessing. Image resize, tokenization, state padding, and action unnormalization belong to PI0Processor; requests passed directly to the engine must already satisfy that contract.

Full example