OpenPI Supervised Fine-Tuning#
OpenPI π₀ / π₀.₅ vision-language-action models.#
Run full-parameter or LoRA SFT on π₀ / π₀.₅ in RLinf. Policies use
model_type: openpi, a PyTorch port aligned with the JAX reference
(the official OpenPI PyTorch code is not). SFT is the usual RL cold start:
imitate demonstrations first, then continue with RL from a stronger prior.
Recipes#
Each experiment YAML imports a path-free model template through Hydra
defaults, then overrides paths and task fields. Replace /path/to/...
before launch.
Task |
Experiment config ( |
Model template |
Launch name |
|---|---|---|---|
LIBERO π₀ |
|
|
|
Real-world Franka bin-relocation |
|
|
|
Custom LeRobot |
|
|
|
BEHAVIOR-1K π₀.₅ |
|
|
|
RoboTwin adjust_bottle π₀ |
|
|
|
For dual-Franka SFT and deployment, see Using Dual Franka with OpenPI PyTorch.
π₀ · π₀.₅
Full SFT · LoRA
LeRobot · BEHAVIOR
1+ nodes · GPUs
bash examples/sft/run_vla_sft.sh <launch-name> → watch the training loss.Datasets#
Select the data format with actor.model.openpi.config_name:
|
Dataset / environment |
|---|---|
|
LIBERO |
|
Real-world Franka |
|
Real-world Dual YAM |
|
RoboTwin (ALOHA) |
|
BEHAVIOR-1K (streaming loader) |
|
ManiSkill |
|
MetaWorld |
|
CALVIN |
|
Custom LeRobot |
Custom LeRobot dataset#
Set
config_nameinexamples/sft/config/custom_sft_openpi.yaml:actor: model: openpi: task: sft config_name: "pi0_custom"
Register
pi0_custominrlinf/models/embodiment/openpi/dataconfig/__init__.py(a template is already there):TrainConfig( name="pi0_custom", model=pi0_config.Pi0Config(), data=CustomDataConfig( repo_id="physical-intelligence/custom_dataset", base_config=DataConfig(prompt_from_task=True), assets=AssetsConfig(assets_dir="checkpoints/torch/pi0_base/assets"), extra_delta_transform=False, action_train_with_rotation_6d=False, ), pytorch_weight_path="checkpoints/torch/pi0_base", ),
Adapt
CustomDataConfiginrlinf/models/embodiment/openpi/dataconfig/franka_dataconfig.pyto your keys and action space.
Normalization statistics#
For a newly collected LeRobot dataset, compute norm_stats for state
and actions before SFT (especially important for real-robot data):
# Local dataset directory (contains meta/info.json):
python toolkits/lerobot/calculate_norm_stats.py \
--config-name pi0_realworld \
--repo-id /path/to/realworld_franka_bin_relocation
# Or a Hugging Face repo id (cached under ~/.cache/huggingface/lerobot):
python toolkits/lerobot/calculate_norm_stats.py \
--config-name pi0_realworld \
--repo-id realworld_franka_bin_relocation
--repo-idis a local path or a LeRobot HF repo id;HF_LEROBOT_HOMEchanges the cache parent.--config-namemust match the training dataconfig.calculate_norm_stats.pywrites to./assets/<config_name>/<repo_id>/norm_stats.json(TrainConfig.assets_dirs / repo_id). Training and eval do not look next to SFTfull_weights.ptfor that file. Setopenpi_data.norm_stats_pathto thatnorm_stats.json. If the key is omitted, OpenPI loads{assets_dir}/{asset_id}/norm_stats.jsonfrom the TrainConfig (aftermodel_pathis applied) and logs a warning. The RoboTwin recipe pins stats beside the base weights atphysical-intelligence/robotwin/<task>/norm_stats.json.
If standard deviations are tiny or the q99–q01 range is very narrow, widening them often stabilizes training, especially when SFT is followed by online RL.
BEHAVIOR does not use that script. Point
actor.model.openpi_data.norm_stats_path at an existing
{assets}/{asset_id}/norm_stats.json.
Installation#
First, clone the RLinf repository:
# Mainland China users can use a mirror for faster cloning:
# git clone https://gh-proxy.com/github.com/RLinf/RLinf.git
git clone https://github.com/RLinf/RLinf.git
cd RLinf
Then set up the dependencies with one of the two methods below — a prebuilt
Docker image (recommended) or a custom environment. The general setup
(prerequisites, GPU drivers, the in-image switch_env helper, mirrors, and
troubleshooting) is documented once in Installation;
the commands in this recipe only differ in the Docker image tag and the
--env value.
Option 1: Docker — image agentic-rlinf0.4-maniskill_libero:
docker run -it --rm --gpus all \
--shm-size 20g \
--network host \
--name rlinf \
-v .:/workspace/RLinf \
rlinf/rlinf:agentic-rlinf0.4-maniskill_libero
# Mainland China mirror: infinigence-ai-registry.cn-beijing.cr.aliyuncs.com/rlinf/rlinf:agentic-rlinf0.4-maniskill_libero
source switch_env openpi
Option 2: Custom environment:
# Add --use-mirror in mainland China.
bash requirements/install.sh embodied --model openpi --env maniskill_libero
source .venv/bin/activate
--model openpi installs the official OpenPI runtime (transforms and
data pipeline). The trained network is still openpi.
LIBERO and real-world Franka#
Edit data paths and actor.model.model_path in the experiment YAML.
Typical overrides:
data:
train_data_paths: /path/to/libero-data
cluster:
num_nodes: 1
component_placement:
actor,env,rollout: 0-0
actor:
model:
model_path: /path/to/pi0-model
num_action_chunks: 4 # env-executed length, not network horizon
is_lora: False # set True and lora_rank for LoRA
openpi:
task: sft
embed_dtype: bf16
config_name: "pi0_libero" # use pi0_realworld for Franka
Launch:
bash examples/sft/run_vla_sft.sh libero_sft_openpi
bash examples/sft/run_vla_sft.sh realworld_bin_relocation_sft_openpi
Logs and checkpoints go under runner.logger.log_path, saved every
runner.save_interval steps at .../checkpoints/global_step_<N>/.
Pi0.5 Base Checkpoint Download and Conversion#
The π₀.₅ JAX base checkpoint is available at
gs://openpi-assets/checkpoints/pi05_base. The downloaded directory contains
params/ and must be converted to the RLinf OpenPI layout before use as
actor.model.model_path. Replace the YAML’s /path/to/... placeholders
with actual paths on your server.
Run from the RLinf repository root in the OpenPI environment with JAX / Orbax installed. Downloading requires the Google Cloud CLI:
mkdir -p /path/to/checkpoints/jax
gcloud storage cp --recursive \
gs://openpi-assets/checkpoints/pi05_base /path/to/checkpoints/jax/
Prepare task-specific norm_stats.json first (see normalization statistics
above). This example uses BEHAVIOR’s 32-step horizon. --input-model must
point to the directory containing params/:
python -m rlinf.utils.ckpt_convertor.openpi.convert --mode jax_to_openpi \
--input-model /path/to/checkpoints/jax/pi05_base \
--input-norm-stats /path/to/task/norm_stats.json \
--output-model /path/to/pi05_base_openpi \
--output-norm-stats /path/to/pi05_base_openpi/task/norm_stats.json \
--action-dim 32 \
--action-horizon 32 \
--max-token-len 200
The converter defaults to π₀.₅; do not add --no-pi05. Other π₀.₅ SFT
recipes use the same workflow with --action-horizon matching their existing
configs: 10 for ManiSkill RLT, and 20 for Franka RLT and dual-Franka. Conversion
does not require changing action dimensions, image counts, or training settings
in the YAML.
Verify that the output directory contains model.safetensors and
config.json. Replace the actor.model.model_path placeholder with that
output directory and point actor.model.openpi_data.norm_stats_path at the
statistics for the same task, for example:
actor:
model:
model_path: /path/to/pi05_base_openpi
openpi_data:
norm_stats_path: /path/to/pi05_base_openpi/task/norm_stats.json
Checkpoints already in the RLinf OpenPI layout need no further conversion.
Official OpenPI PyTorch checkpoints (with paligemma_with_expert.* keys)
still require openpi_pytorch_to_openpi even when they contain
model.safetensors. See rlinf/utils/ckpt_convertor/openpi/README.md for
that command.
Pi0.5 + BEHAVIOR-1K#
Filesystem paths live in the experiment config; the model shape comes from
model/pi0_5:
defaults:
- model/pi0_5@actor.model
- hybrid_engines/fsdp@actor.fsdp_config
The streaming loader reads only data: (no hidden defaults). Change at least:
data:
train_data_paths: /path/to/2025-challenge-demos
behavior_dataset_root: /path/to/2025-challenge-demos
repo_id: "behavior-1k/2025-challenge-demos"
modalities: ["rgb"]
num_workers: 8
tasks: ["turning_on_radio"]
use_skill: false
task_subtasks:
turning_on_radio:
- "move to radio"
- "pick up radio from coffee table"
- "press radio"
- "place radio on coffee table"
actor:
model:
model_path: /path/to/pi05_base_openpi # new-format fp32 base weights
openpi:
task: sft
embed_dtype: bf16
openpi_data:
norm_stats_path: /path/to/assets/behavior-1k/2025-challenge-demos/norm_stats.json
use_skill: falsetrains on the main-task text;trueuses per-frame skill text fromtask_subtasks(collapsed orchestrators in the dataset are not valid labels).fine_grained_level/tolerance_scontrol streaming time alignment.
bash examples/sft/run_vla_sft.sh behavior_sft_openpi_pi05
Pi0 + RoboTwin#
Uses the official OpenPI / LeRobot map-style loader with
config_name: pi0_aloha_robotwin. Example data:
adjust_bottle;
matching HF SFT weights:
RLinf-Pi0-NEW-RoboTwin-SFT-adjust_bottle.
data:
train_data_paths: /path/to/robotwin-data
num_workers: 4
actor:
model:
model_path: /path/to/pi0_base_pytorch_new
num_action_chunks: 50
action_dim: 14
openpi:
task: sft
embed_dtype: bf16
config_name: "pi0_aloha_robotwin"
num_images_in_input: 3
openpi_data:
norm_stats_path: ${actor.model.model_path}/physical-intelligence/robotwin/adjust_bottle/norm_stats.json
14-D ALOHA actions and three cameras; OpenPI pads actions to 32-D before the
model. openpi_data.norm_stats_path pins SFT and eval to the same
norm_stats.json. Change the task name in that path when switching tasks.
bash examples/sft/run_vla_sft.sh robotwin_adjust_bottle_sft_openpi
Evaluation#
SFT writes full_weights.pt under
.../checkpoints/global_step_<N>/actor/model_state_dict/. openpi
eval can load that .pt (or the global_step_<N> directory) directly:
wrapper / FSDP prefixes are stripped in memory, and the eval YAML supplies the
model shape. You do not need to convert to model.safetensors first.
Do not copy training openpi.task: sft into eval. Eval configs must use
task: eval (that selects Pi0Eval and observation transforms). Keep
config_name and num_action_chunks aligned with training.
Norm stats also do not travel with full_weights.pt. Stock eval YAMLs
often interpolate openpi_data.norm_stats_path as
${rollout.model.model_path}/.../norm_stats.json, which assumes a converted
directory that already bundles stats. If you only retarget model_path at
an SFT checkpoint, also point stats back at the training copy (RoboTwin: next
to the base weights at physical-intelligence/robotwin/<task>/norm_stats.json;
BEHAVIOR: the training openpi_data.norm_stats_path).
# Example: evaluate the SFT .pt in place (keep training-time norm stats)
bash evaluations/run_eval.sh robotwin robotwin_adjust_bottle_openpi_eval \
rollout.model.model_path=/path/to/logs/.../checkpoints/global_step_30000 \
rollout.model.openpi_data.norm_stats_path=/path/to/pi0_base_pytorch_new/physical-intelligence/robotwin/adjust_bottle/norm_stats.json
Eval steps: BEHAVIOR-1K and
RoboTwin. To export a shareable
bare Pi0 directory (model.safetensors + config.json + copied
norm_stats.json), use sft_to_openpi; commands are in
rlinf/utils/ckpt_convertor/openpi/README.md.
Visualization#
Watch the training loss. Metric definitions: Training metrics.
tensorboard --logdir ./logs