Supervised Fine-Tuning with PyTorch OpenPI (Pi0.5) on BEHAVIOR#
This page explains how to run supervised fine-tuning (SFT) of the
self-contained PyTorch OpenPI Pi0.5 flow-matching VLA on the
BEHAVIOR-1K task with the RLinf framework. The model is a pure-PyTorch
re-implementation of the Pi0.5 architecture (dual-expert Gemma + SigLIP with a
flow-matching action head), registered in RLinf under
model_type: openpi_pytorch. SFT is typically the first stage before
reinforcement learning: the model imitates high-quality demonstrations so that
RL can continue optimization from a strong prior.
Contents#
What the PyTorch OpenPI SFT flow is and how it is configured
The precision contract used by the FSDP optimizer and mixed-precision compute
The streaming BEHAVIOR data-loader keys and norm-stats / tokenizer settings
How to launch training and convert the resulting checkpoints for evaluation
What it is#
The openpi_pytorch model is a self-contained PyTorch port of the Pi0.5
flow-matching VLA. It is worth emphasizing that the PyTorch implementation
shipped in the official openpi repository is not numerically aligned with its
JAX reference, whereas this port is numerically aligned with the JAX
implementation. Unlike the JAX/LeRobot-backed OpenPI path (see
OpenPI Supervised Fine-Tuning), it builds the model shape directly from a small set of
config fields (no config.json is read at construction time) and is wired for
BEHAVIOR-1K out of the box. During SFT the policy is trained to predict the
32-step, 23-dim action chunk for the dual-arm R1 Pro robot from the BEHAVIOR
demonstrations, using the flow-matching denoising objective.
Configuration#
The example is split into a reusable, path-free model template and an experiment config that supplies the filesystem paths:
Experiment config:
examples/sft/config/behavior_pi05_vla.yamlModel template:
examples/sft/config/model/pi0_5_pytorch.yaml
The experiment config pulls in the model template through Hydra defaults:
defaults:
- model/pi0_5_pytorch@actor.model
- training_backend/fsdp@actor.fsdp_config
- override hydra/job_logging: stdout
Precision contract#
The PyTorch OpenPI SFT recipe deliberately separates the load dtype from the compute dtype:
The model template sets
actor.model.precision: fp32(inpi0_5_pytorch.yaml). The fp32 weights are loaded as the FSDP optimizer master, so warmup-LR updates are not lost to bf16 rounding.FSDP
MixedPrecisioncomputes in bf16 while keeping the gradient all-reduce and buffers in fp32:actor: fsdp_config: gradient_checkpointing: True mixed_precision: param_dtype: bf16 # FSDP compute dtype reduce_dtype: fp32 # grad all-reduce stays fp32 buffer_dtype: fp32
param_dtypeis the FSDP compute dtype and is set explicitly to bf16 rather than being interpolated fromactor.model.precision: the load-dtype selector and the compute dtype are independent knobs, so an fp32-master load still computes in bf16.Gradient checkpointing is enabled (
actor.fsdp_config.gradient_checkpointing: True) on the dual-expert Gemma + SigLIP backbone to reduce activation memory.The learning-rate schedule is a reference-exact warmup + cosine decay, selected with
actor.optim.lr_scheduler: openpi_cosine(warmup starts atpeak / (warmup + 1)and cosine-decays tomin_lrovertotal_training_steps).
Streaming data loader#
The BEHAVIOR streaming loader reads all of its parameters directly from the
data: section (there are no hidden defaults):
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
fine_grained_level: 0
tolerance_s: 1.0e-4
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"
Key data fields:
train_data_paths/behavior_dataset_root: root of the BEHAVIOR dataset (the latter defaults to the former).repo_id: BEHAVIOR demonstration repo id (behavior-1k/2025-challenge-demos).modalities: input modalities consumed by the loader (e.g.["rgb"]).num_workers: number of data-loader worker processes.fine_grained_levelandtolerance_s: time-alignment controls for the streaming reader.tasks: the BEHAVIOR task(s) to train on.use_skill: whenfalse, train on the main-task text; whentrue, train on the per-frame REFERENCE skill text selected fromtask_subtasks.task_subtasks: per-task ordered skill labels used to build the index-to-label mapping whenuse_skill: true.
Norm stats and tokenizer#
The normalization statistics and PaliGemma tokenizer live under
actor.model.openpi:
actor:
model:
model_path: /path/to/pi05_base_pytorch_new
openpi:
assets_dir: /path/to/assets
asset_id: "behavior-1k/2025-challenge-demos"
paligemma_tokenizer: /path/to/paligemma_tokenizer/paligemma_tokenizer.model
assets_dir: directory holding the quantile-normalization stats.asset_id: sub-path underassets_dirfor this task’s stats.paligemma_tokenizer: the PaliGemma SentencePiece tokenizer model (resolved from YAML, not hardcoded in code).
The norm stats are resolved at {assets_dir}/{asset_id}/norm_stats.json.
Filesystem paths#
All filesystem paths are set directly in the config as /path/to/...
placeholders. Edit them in examples/sft/config/behavior_pi05_vla.yaml to
point at your own staged assets:
data.train_data_paths/data.behavior_dataset_root: root of the BEHAVIOR streaming dataset.actor.model.model_path: the new-format fp32 base checkpoint the trainer loads.actor.model.openpi.assets_dir: the norm-stats directory.actor.model.openpi.paligemma_tokenizer: the PaliGemma SentencePiece tokenizer model.
Launch scripts#
Run the SFT helper with the BEHAVIOR Pi0.5 config name:
# return to repo root
bash examples/sft/run_vla_sft.sh behavior_pi05_vla
The script forwards the config name to the SFT entry point and writes logs and
checkpoints under the configured runner.logger.log_path. Checkpoints are
saved every runner.save_interval steps under
.../checkpoints/global_step_<N>/.
Converting checkpoints for evaluation#
An SFT-trained checkpoint can be converted into the bare new-format Pi0
layout (the layout the evaluation loader expects) with the OpenPI checkpoint
convertor:
python -m rlinf.utils.ckpt_convertor.openpi.convert --mode sft2new \
--ckpt /path/to/logs/.../checkpoints/global_step_30000 \
--input-norm-stats /path/to/norm_stats.json \
--output-model /path/to/pi05_sft_pytorch_new \
--output-norm-stats /path/to/pi05_sft_pytorch_new/physical-intelligence/behavior/norm_stats.json
The sft2new mode strips the wrapper/FSDP key prefixes, casts floating-point
tensors to bf16 (the new-format eval loader validates that every checkpoint
tensor is bf16), and copies the norm-stats file verbatim. See the convertor
package README at rlinf/utils/ckpt_convertor/openpi/README.md for the other
conversion modes and full flag reference. The converted checkpoint can then be
used to evaluate on BEHAVIOR; see RL with Behavior Benchmark for the eval config and launch
command.