Introduction#
P-BFM (Perceptive Behavioral Foundation Model) is a perceptive behavior foundation model released by RoboParty Lab for humanoid whole-body control across terrain. It maps motion references, target body states, and reward functions into one intent space. A frozen policy combines depth perception with explicit heading and velocity commands to execute those intents on the terrain underfoot.
P-BFM trains with just 40 flat-ground LaFAN motions and online interaction with simulated terrain, without terrain-specific motion demonstrations. The single trained policy transfers zero-shot to a real robot with an onboard depth camera, supporting motion tracking, goal reaching, reward inference, teleoperation, and disturbance recovery.
Resources:
1. Overview#
This section covers how P-BFM works, release contents, and environment setup. By the end, you can install the environment, download motion data, and check the setup with tests and smoke training.
1.1 What P-BFM Can Do#
| Workflow | Purpose | See |
|---|---|---|
| Prepare motion data | Download LaFAN training data or convert your motions to the training format | Section 2 |
| Training | Train from scratch on 8 GPUs or run a quick smoke check | Section 3 |
| Inference | Provide intent through motion references, target poses, or reward functions; run in simulation and record video | Section 4 |
| Terrain evaluation | Execute the same intent on multiple terrains and compare results | Section 5 |
| Export and deployment preparation | Export ONNX and verify deployment input conventions | Section 6 |
1.2 How It Works#
Unified intent space. P-BFM uses forward–backward representations. The Backward encoder maps robot states into intent space: frames of a reference motion, a target pose, or reward-weighted state samples all produce intent vectors z in the same space.
Perceptive execution. The Actor reads proprioceptive observations, intent z, and depth images (8 frames of 18 × 32) to execute the intent on the current terrain. The same intent produces different actions on stairs, slopes, or boxes.
Explicit root commands. Latent-only control is insufficiently precise about global travel distance and direction. P-BFM supplies heading and planar velocity commands alongside z to control root motion separately.
Velocity estimation. The policy includes a depth-supervised velocity estimator (721 → 512 → 256 → 3) that outputs current velocity in the yaw frame. At deployment, the policy uses this estimate without simulator ground-truth velocity.
Training. Unsupervised reinforcement learning uses 40 flat-ground LaFAN motions as behavioral priors while the robot interacts online with simulated terrain. Four independent Critics correspond to the behavior prior (QD), auxiliary rewards (QAux), heading (QH), and velocity (QV).
1.3 Release Contents#
Released: Code for training, inference, terrain evaluation, and ONNX export; training configuration (
pbfm/configs/legacy_large.json); depth input contract (pbfm/configs/depth_contract.json); robot models and configurations.Downloaded separately: LaFAN training motion data (Section 2.1).
Not provided: Pretrained weights. Neither the repository nor project page provides a weight download; inference and export require your own run directory trained as in Section 3. To preview behavior, open the project's interactive browser demo and try motion references, target poses, and reward presets on a shared terrain map (a desktop browser is recommended).
Not included: A controller that sends joint commands to the robot. Section 6 explains what the exported model includes and what you must implement.
Note
P-BFM is based on UFO and retains its upstream CC BY-NC 4.0 license, restricted to noncommercial use.
1.4 System Requirements#
| Item | Requirement |
|---|---|
| Operating system | Linux |
| Python | 3.10 |
| Package manager | uv |
| Main dependencies | MJLab 1.4.0, MuJoCo 3.8, PyTorch 2.7 |
| GPU | Full training: 8 GPUs, each running 1024 parallel environments with replay also on GPU. Inference defaults to cuda:0. Unit tests can run on CPU; GPU-dependent tests skip automatically |
| Disk | Each rank writes its own replay into checkpoints; reserve sufficient space before long runs |
1.5 Installation#
git clone https://github.com/Roboparty/P-BFM.git
cd P-BFM
uv sync --extra test--extra test also installs pytest and onnxruntime. ONNX export in Section 6 uses onnxruntime to verify the output, so installing it now is recommended.
1.6 Run Quick Checks#
Download training data first (Section 2.1), then run unit tests:
bash scripts/download_data.sh
PYTHONPATH=. uv run python -m pytest testsTests requiring external data or a GPU skip automatically when unavailable. Passing tests verifies code behavior, not policy convergence or real-robot safety.
With 8 available GPUs, also run smoke training as in Section 3.3 to check the training pipeline.
1.7 Directory Layout#
P-BFM/
├── pbfm/
│ ├── train.py # Training entry point
│ ├── configs/ # Training templates, simulation, robot, and depth contracts
│ ├── data/ # Robot models, sample data, and data downloaded in Section 2.1
│ ├── inference/ # Tracking, goal, reward inference, and ONNX export
│ ├── evaluation/ # Terrain evaluation and diagnostics
│ └── motion/ # Motion reading, conversion, and manifests
├── scripts/ # Training, download, export, and verification commands
├── tests/ # Tests
└── docs/ # English documentationSee Section 3.6 for the training output (--work-dir) layout.
2. Motion Data#
This section prepares training motion data: download the authors' LaFAN data or convert your own motions into a format P-BFM can read. Run commands from the repository root.
2.1 Download LaFAN Data#
bash scripts/download_data.shThe script downloads two files from the Hugging Face dataset xuewang/UFO-MotionData, verifies SHA-256, and places them in pbfm/data/:
| File | Purpose |
|---|---|
pbfm/data/lafan_29dof_10s-clipped.pkl |
LaFAN motions split into 10-second clips; default data for training and inference |
pbfm/data/lafan_29dof.pkl |
LaFAN motion file without the “10-second clipping” suffix |
Existing files with correct hashes are skipped. The script needs the hf command or Python package huggingface_hub, already present after uv sync.
Tip
When no --data-path is found, inference defaults to pbfm/data/lafan_29dof_10s-clipped.pkl relative to the current directory. You can therefore omit the path when running inference from the repository root. Training requires an explicit --data-path; see Section 3.2.
2.2 Prepare Your Motions#
P-BFM reads robot-state CSV or NPZ motions. The CSV example at pbfm/data/examples/g1_robot_state_sample.csv has one frame per row:
| Column | Contents |
|---|---|
time |
Timestamp |
root_pos_x, root_pos_y, root_pos_z |
Root position |
root_quat_x, root_quat_y, root_quat_z, root_quat_w |
Root orientation quaternion (xyzw order) |
dof_0 … dof_28 |
29 joint angles in radians |
Check that the data can be read correctly:
uv run python -m pbfm.motion.inspect \ --robot pbfm/configs/robots/g1_29dof.yaml \ --source <your-CSV-or-NPZ-file-or-directory>Generate a data manifest and build the training cache:
uv run python -m pbfm.motion.build \ --robot pbfm/configs/robots/g1_29dof.yaml \ --source <your-CSV-or-NPZ-file-or-directory> \ --name <dataset-name> \ --out <manifest-path>.yamlUse
--data-manifest <manifest-path>.yamlinstead of--data-pathfor training; they are mutually exclusive.
To mix multiple sources, pass multiple files to --data-path and specify weights with --data-mix-weights, for example --data-mix-weights 0.95 0.05.
Note
For terrain training (anything except --terrain-mode plane), preflight checks that each motion's maximum horizontal displacement, plus perception radius and margin, stays within the terrain's safe radius. Motions exceeding it stop training with an error; success logs terrain coverage preflight passed.
3. Training#
This section trains P-BFM from scratch. Full training requires 8 GPUs. Run commands from the repository root.
3.1 Set Environment Variables#
export PYTHONPATH="$PWD${PYTHONPATH:+:$PYTHONPATH}"
export OMP_NUM_THREADS=1 MUJOCO_GL=egl NCCL_NVLS_ENABLE=0
export UFO_CACHE_DIR=/path/to/writable-disk/pbfm-cacheUFO_CACHE_DIR is the root for temporary files, PyTorch / Triton / CUDA / Warp compilation caches, and expert-data caches. Use a writable disk with enough space; the default is cache/ under the current directory.
3.2 Train from Scratch#
uv run python -m torch.distributed.run --standalone --nnodes=1 --nproc_per_node=8 \
scripts/train_legacy_scratch.py \
--gpu-ids single --terrain-mode rp1_simple --seed 4831 \
--num-envs 1024 --num-env-steps 192020480 \
--data-path "$PWD/pbfm/data/lafan_29dof_10s-clipped.pkl" \
--work-dir /path/to/new-runReplace /path/to/new-run with a new output directory. torch.distributed.run launches 8 local processes, each using one GPU and 1024 environments, for a total of 192,020,480 global environment steps. --gpu-ids must be single; torch.distributed.run handles multiple GPUs.
Note
Always provide --data-path or --data-manifest. The configuration template's default data path is specific to the authors' machine and does not exist elsewhere.
Note
Use scripts/train_legacy_scratch.py for training from scratch. It starts the terrain curriculum at step 0 and rejects work directories containing checkpoints. Running python -m pbfm.train directly uses the continuation schedule: 384,040,960 total steps, with the terrain curriculum starting at step 192,020,480.
3.3 Run a Quick Smoke Check#
Append --smoke to the Section 3.2 command and use a new --work-dir:
uv run python -m torch.distributed.run --standalone --nnodes=1 --nproc_per_node=8 \
scripts/train_legacy_scratch.py \
--gpu-ids single --terrain-mode rp1_simple --seed 4831 \
--num-envs 1024 --num-env-steps 192020480 \
--data-path "$PWD/pbfm/data/lafan_29dof_10s-clipped.pkl" \
--work-dir /path/to/smoke-run --smokeSmoke mode runs 32,768 global steps with at most 16 environments per process and reduced replay capacity. It disables training-time evaluation, replay checkpoint writing, and W&B. It only verifies that the pipeline runs.
Note
Smoke replay is small and does not represent full-training GPU memory usage. Before full training, ensure each GPU can hold full replay capacity (default 5,120,000 transitions per rank), model and optimizer states, and training batches, and that disk space can hold every rank's replay checkpoints.
3.4 Training Parameters#
| Parameter | Purpose | Default |
|---|---|---|
--terrain-mode |
Training terrain: rp1_simple, plane, flat, slope, stairs, stairs_up, stairs_down, rough, platforms, mixed |
rp1_simple |
--num-envs |
Parallel environments per process | 1024 |
--num-env-steps |
Total global environment steps (scratch entry point) | 192020480 |
--seed |
Random seed | 4831 |
--checkpoint-every-steps |
Checkpoint interval in global steps | 9600000 |
--tracking-eval-every-steps |
Flat-ground tracking evaluation interval during training | 9600000 |
--same-z-eval-every-steps |
Multi-terrain evaluation interval during training; ≤ 0 disables it | 0 |
--buffer-size |
Replay capacity per process | 5120000 |
--disable-dr / --disable-obs-noise |
Disable domain randomization / observation noise | Both enabled |
--use-wandb |
Log to W&B (log in separately first) | Off |
plane is the flat-ground control condition: the surface is completely flat, but terrain observations are retained. For W&B, append --use-wandb --wandb-project PBFM --wandb-run-name pbfm.
3.5 Training Terrain#
The default rp1_simple terrain consists of 5 m square tiles, with the robot starting at each tile's center. Terrain proportions are:
| Terrain | Proportion | When starting at the center |
|---|---|---|
flat |
20% | Flat ground |
perlin_rough |
10% | Rough ground |
low_stairs_down |
20% | Ascending stairs |
low_stairs_up |
20% | Descending stairs |
low_platforms |
10% | Low platforms |
hf_pyramid_slope_inv |
10% | Slope |
boxes |
10% | Box obstacles |
Steps are 10–20 cm high, roughness amplitude is 2–6 cm, and boxes are 5–15 cm high. Difficulty increases through a curriculum starting at step 0 for scratch training, advancing every 19,200,000 global steps.
Note
Stair names describe elevation changes when moving toward the tile center. The robot starts at the center and moves outward, so low_stairs_down actually means ascending and low_stairs_up descending. Check the start, heading, and actual geometry before labeling videos.
3.6 Training Outputs#
<work-dir>/
└── checkpoint/
├── model/ # Model configuration and weights (model.safetensors)
├── train_status.json # Training progress
└── buffers/
└── train_rank_<r>/ # Replay for each rankInference and ONNX export need only the model. Reward inference in Section 4.3 also requires replay.
Resuming depends on replay format and depth-observation conventions:
Training replay must record
explicit_next=true. If absent, load only the model into a new training directory, rebuild replay with a collection warmup, then start updates.Do not modify replay metadata to force incompatible data to load.
4. Inference#
This section runs three inference modes with a trained policy in simulation: motion tracking, goal reaching, and reward inference. They generate intent z from motion references, target poses, and reward functions, respectively. Run from the repository root. --model-folder points to the training --work-dir, which must contain checkpoint/.
4.1 Motion Tracking#
uv run python -m pbfm.inference.tracking --model-folder /path/to/run \
--motion-list 20 --save-mp4The policy encodes the reference motion into intent frame by frame and tracks it in simulation. --save-mp4 produces a side-by-side video: reference on the left and policy execution on the right.
To track on a training terrain tile, for example low_stairs_down at difficulty row 7:
uv run python -m pbfm.inference.tracking --model-folder /path/to/run \
--motion-list 20 --save-mp4 \
--terrain-family low_stairs_down --difficulty-row 7Terrain geometry is read from the checkpoint's training configuration.
| Parameter | Purpose | Default |
|---|---|---|
--motion-list |
Motion indices to track; accepts multiple values | 20 |
--save-mp4 |
Save a comparison video | No |
--terrain-family |
Choose a training terrain tile (Section 3.5); if omitted, retain the checkpoint's environment settings | Omitted |
--difficulty-row |
Difficulty row used with --terrain-family |
7 |
--reset-height-mode |
reference: retain reference root height; terrain: add the terrain tile origin height |
terrain when a tile is selected, otherwise reference |
--velocity-context |
auto: generate reference velocity commands following checkpoint rules; valid: explicitly use reference velocity; invalid: supply no velocity command |
auto |
--max-steps |
Maximum steps for quick previews | Unlimited |
--device |
Inference device | cuda:0 |
--export-onnx |
Export ONNX to <model-folder>/exported/ before inference |
Yes |
Outputs:
| File | Contents |
|---|---|
<model-folder>/tracking_inference/zs_<index>.pkl |
Intent-vector sequence for the motion |
<model-folder>/tracking_inference/context_<index>.npz |
Heading, velocity commands, and validity flags aligned with intent |
<model-folder>/tracking_inference/tracking_<index>.mp4 |
Comparison video (requires --save-mp4) |
<model-folder>/exported/FBcprAuxModel.onnx, FBcprAuxModel.meta.json, backward_encoder.onnx |
Exported policy, metadata, and Backward encoder |
4.2 Goal Reaching#
uv run python -m pbfm.inference.goal --model-folder /path/to/run --save-mp4The policy reaches a sequence of target poses taken from specific motion-data frames. The default list, pbfm/data/robots/g1/goal_frames_lafan29dof.json, contains 8 groups, each specifying a motion name (motion_name), motion index (motion_id), and frame indices (frames). Replace it with --goal-json.
| Parameter | Purpose | Default |
|---|---|---|
--goal-json |
Target-pose list | Default file above |
--episode-len |
Total steps | 2100 |
--goal-switch-interval |
Steps between target changes | 100 |
--save-mp4 |
Save video | No |
Outputs are <model-folder>/goal_inference/goal_reaching.pkl (intent vectors for each goal) and goal_inference/videos/goal.mp4.
4.3 Reward Inference#
uv run python -m pbfm.inference.reward --model-folder /path/to/run --save-mp4Reward inference rescores training replay samples with the specified reward function and computes intent vectors. It therefore requires training replay: train_reduced, train_rank_<n>, or train under checkpoint/buffers/. Smoke training does not write replay and cannot support reward inference.
By default, 38 tasks run in sequence; select a subset with --tasks. Examples of task names:
| Task name | Meaning |
|---|---|
move-ego-<angle>-<speed> |
Travel direction relative to the robot's heading (degrees) and target speed, such as move-ego-0-0.7 or move-ego-90-0.3; move-ego-0-0 requests zero speed |
move-ego-low<height>-<angle>-<speed> |
As above, while maintaining a lower standing height, such as move-ego-low0.5-0-0 |
rotate-z-<angular-velocity>-<pelvis-height> |
Rotate about the vertical axis; the sign indicates direction, for example rotate-z-5-0.5 and rotate-z--5-0.5 |
raisearms-<left>-<right> |
Hold each arm in poses such as l or m |
crouch-0, sitonground |
Crouch or sit on the ground |
| Parameter | Purpose | Default |
|---|---|---|
--tasks |
Task subset | All 38 |
--num-samples |
Samples drawn from replay | 150000 |
--episode-length |
Steps per task | 500 |
--skip-rollouts |
Compute intent only, without simulation or video | No |
--buffer-rank / --buffer-path |
Select a rank's replay / specify the replay directory directly. If train_reduced exists, it takes priority and --buffer-rank has no effect |
0 / omitted |
--max-workers |
Parallel rescoring processes | 24 |
Outputs are <model-folder>/reward_inference/reward_locomotion.pkl and one video per task (requires --save-mp4).
4.4 Heading and Velocity Commands#
Alongside intent z, the policy reads heading and planar velocity commands. The three inference modes handle them differently:
Motion tracking: Intent advances frame by frame through an 8-frame reference window, shortened at the motion's end. Heading and valid planar velocity commands come from the same aligned reference motion.
Goal reaching and reward inference: Reference velocity commands are not generated automatically.
Valid versus invalid: A valid zero velocity means “stop”; invalid means “no velocity command.” For custom motion names, explicitly choose the intended
--velocity-context.
When saving or sending intent, keep the vector, heading and velocity, validity flags, frame rate, and coordinate conventions together. The intent vector alone is not a complete input to a command-conditioned policy.
5. Terrain Evaluation#
This section executes the same intent on different terrains and compares behavior to assess terrain adaptation. Run commands from the repository root.
uv run python -m pbfm.evaluation.terrain --model-folder /path/to/run \
--prompt-type tracking --terrains flat,slope,stairs,rough \
--output results/terrain_transfer_results.json| Parameter | Purpose | Default |
|---|---|---|
--prompt-type |
Intent source: tracking, goal, or reward |
Required |
--terrains |
Comma-separated terrain list. Accepts flat, slope, stairs, stairs_up, stairs_down, rough, platforms, course, and the training tile names in Section 3.5 |
flat,slope,stairs,rough |
--rp1-difficulty-row |
Training terrain difficulty row (0–9) | 4 |
--motion-id |
Motion index for tracking |
0 |
--goal-index |
Target index for goal |
0 |
--reward-task |
Task for reward |
move-ego-0-0.7 |
--episode-length |
Steps per terrain | 500 |
--seed |
Random seed | 4728 |
--output |
Result JSON path | terrain_transfer_results.json |
Results are written as JSON and a same-named CSV in the same location, with one row per terrain.
See all options with uv run python -m pbfm.evaluation.terrain --help.
6. Export & Deployment#
This section exports ONNX models for deployment and explains input conventions and the components you must implement. The repository does not include a real-robot controller. Run commands from the repository root.
6.1 Export the Policy#
uv run python -m scripts.export_depth_policy_onnx \
--checkpoint /path/to/run/checkpoint --output /path/to/export/policy.onnxThe script loads the checkpoint on CPU, exports ONNX, and verifies it with the ONNX checker and onnxruntime comparisons. Success prints a verification result beginning with Verified <output-path>:.
The exported policy.onnx is a complete deployment graph, not a split model containing only the action network:
| Included in ONNX | Not included; implement yourself |
|---|---|
| Depth encoder | Real-camera depth preprocessing |
| Velocity estimator | Depth and observation histories |
| Observation normalization | Frame alignment of intents and commands |
| Actor | Action scaling and joint PD control |
Inputs contain neither Critics nor simulator-only velocity labels. Input names and shapes may differ across checkpoints; use the metadata generated at export rather than another checkpoint's interface.
6.2 Export the Backward Encoder#
The Backward encoder maps robot states into intent space and computes intent vectors from reference motions or target poses at deployment. Export it separately:
uv run python -m scripts.export_backward_encoder_onnx \
--model-folder /path/to/run --output /path/to/export/backward_encoder.onnx --device cpu6.3 Depth Input Contract#
The depth contract is recorded in pbfm/configs/depth_contract.json; deployment preprocessing must match it:
| Item | Contract |
|---|---|
| Depth quantity | Optical z depth (optical_z) |
| Raw resolution | 36 × 64 |
| Cropped resolution | 18 × 32 |
| Temporal history | 8 frames, with sample ages in sampled_ages (35, 30, 25, 20, 15, 10, 5, 0) |
| Invalid depth | Treat as 2.5 m |
| Processing | max cropping and Gaussian smoothing with σ = 1.0 (profile optical_z_maxcrop18_reset_v2) |
| Mounting errors simulated in training | Position: 1 cm; roll / pitch / yaw: 2° / 10° / 2°; delay: 0–1 frames |
Note
ONNX depth input is float in [0, 1]. Convert simulated uint8 depth to float32 and divide by 255 exactly once. Do not divide already normalized float depth again.
6.4 Verify Exports with Real-Robot Traces#
scripts/verify_depth_policy_trace.py uses recorded robot observations to compare actions and velocity estimates from ONNX and the original checkpoint:
uv run python -m scripts.verify_depth_policy_trace \
--checkpoint /path/to/run/checkpoint --onnx /path/to/export/policy.onnx \
--traces <trace-file> --output <report-path>This checks interface consistency, not control performance. ONNX parity tests also cannot validate real-robot integration steps such as camera preprocessing, temporal histories, action scaling, or PD control.
The repository also includes a read-only recorder, pbfm/perception/capture.py, that synchronizes depth-camera images and robot states for perception debugging. It neither loads a policy nor sends motor commands.
6.5 Before Real-Robot Deployment#
Camera-depth preprocessing matches Section 6.3 and has been verified with real traces (Section 6.4)
Depth is normalized exactly once, with inputs in [0, 1]
Intent vectors, heading and velocity commands, validity flags, frame rate, and coordinate conventions are sent together (Section 4.4)
Input names and shapes match the metadata exported from this checkpoint
Action scaling, joint order, and PD gains match the training robot configuration
Tracking and terrain evaluation have run in simulation with the same checkpoint (Sections 4 and 5)
The robot is suspended or protected for its first run, and the hardware emergency stop has been tested separately from software stopping