Overview
pi0 turns camera views, task text, and robot state into a chunk of future actions. Itsws1 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 entersengine.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:
1 + chunk_size tokens: one state token followed by the action chunk.
CUDA graphs
The fixed request geometry makes CUDA graph capture possible. WithRuntimeConfig(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.
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
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:
Benchmarking and profiling
Usebenchmark/bench_n_batch_ws1_pi0.py to compare batch sizes under the same scheduler configuration:
--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.

