Skip to main content
Version: 10

Train a VLA Policy on Your Own RunPod

Required Version
This feature will not be released until MoveIt Pro version 10.0.

The train-model MoveIt Pro skill fine-tunes a pi0.5 or SmolVLA policy on a LeRobot dataset using a GPU pod you rent on RunPod, and hands you back a checkpoint on your own Hugging Face Hub account. There is no local GPU requirement and no PickNik-hosted training service. You bring a RunPod account and a Hugging Face account, and the skill drives both from a Claude Code conversation.

The skill supports two policies:

  • pi0.5 (lerobot/pi05_base) is Physical Intelligence's vision-language-action model, built on a PaliGemma backbone and trained for open-world generalization across robots and environments. See Physical Intelligence's pi0.5 announcement and the accompanying paper.
  • SmolVLA (lerobot/smolvla_base) is Hugging Face's compact, 450-million-parameter vision-language-action model, trained on community-collected LeRobot datasets and designed to run affordably on consumer-grade hardware. See the SmolVLA announcement.

This page walks through invoking the skill end to end. For the full script reference, parameter list, and every gotcha, see SKILL.md and REFERENCE.md in the skill's own repository.

What This Costs You

This flow runs on compute and storage you pay for directly, in accounts you own:

  • RunPod GPU time, billed per hour for as long as the pod runs. Expect roughly a dollar an hour for the default L40S and A100 shortlist, for however many hours a 50,000-step pi0.5 LoRA run takes. preflight.sh prices your shortlist and estimates the run before you launch anything.
  • RunPod network volume storage, billed per GB-month for as long as the volume exists. It defaults to 100 GB and keeps billing after every pod is deleted, since it holds the cached model weights and training environment for your next run.
  • Hugging Face Hub storage for the dataset and output model repos, under your account's own plan.

Prerequisites

  • A RunPod account with billing set up, and its API key.

  • A Hugging Face account with a write-scoped access token (or a fine-grained token scoped to the output model repo or its namespace).

  • Claude Code (or another CLI coding agent) with the MoveIt Pro skills installed. A MoveIt Pro installation puts them in ~/moveit_pro, where an agent launched from that folder discovers them; anywhere else, run npx skills add PickNikRobotics/moveit-pro-skills -y.

  • A LeRobot v3 dataset to train on: your own, converted with Collect Training Data with Trainer or Collect Training Data with Behaviors, or a public LeRobot dataset already on Hugging Face Hub. A third-party Hub dataset must already carry the quantile statistics (q01/q99) pi0.5 normalizes by, since preflight neither checks for them nor backfills them; a dataset missing them fails only after a paid pod is running. If you can't confirm they're there, pull the dataset down and run the skill's backfill_stats.py over it first, from the train-model skill's own directory (~/moveit_pro/.claude/skills/train-model for a MoveIt Pro installation, or wherever npx skills add installed it):

    python scripts/backfill_stats.py <dataset-dir>
    What Are Quantile Statistics?

    Quantile statistics are per-dimension percentiles (q01, q10, q50, q90, q99) of a dataset's observation.state and action values, computed once from the recorded episodes. pi0.5 normalizes both by these quantiles (q01/q99) instead of by mean and standard deviation, and will not train without them. The MoveIt Pro converter writes only min/max/mean/std/count on its own, so backfill_stats.py computes the full set during local dataset preparation.

  • runpodctl, the hf CLI, jq, curl, and bash 4 or newer on the machine running the skill. Install the hf CLI with:

    pip install 'huggingface_hub[cli]'
  • A local lerobot install to prepare the dataset. This step is CPU only; no GPU is needed.

    pip install 'lerobot[pi,peft,training]==0.6.0'
  • Know which of your dataset's cameras is the base view and which is the wrist view; you confirm this mapping during preflight.

Know the base model's license before you start. The default lerobot/pi05_base checkpoint carries the Gemma Terms of Use, which attach conditions to distributing or hosting anything fine-tuned from it.

Run the Skill

Open Claude Code in your workspace and ask it to train a policy, for example:

Use the train-model skill to fine-tune pi0.5 on my dataset lab_sim_bottle_picks,
pushing the checkpoint to my-hf-username/bottle-picks-policy.

This page uses pi0.5 as its worked example. To fine-tune SmolVLA instead, pass --policy-type smolvla to preflight.sh; that is the POLICY_TYPE parameter documented in SKILL.md. The overall flow is the same, but the pi0.5-specific details on this page do not all carry over: the quantile statistics, the lerobot/pi05_base default and its Gemma terms, and the cost estimate below are tied to pi0.5. Check SKILL.md for the SmolVLA equivalents before relying on them.

The skill runs the workflow from SKILL.md, pausing where it needs your input:

  1. Prepares the dataset locally. When you prepare your own dataset, this step backfills the quantile statistics pi0.5 needs (the MoveIt Pro converter doesn't write them), merges multiple recorded prompts into one dataset if you have more than one, and pushes the result to Hugging Face Hub as a dataset repo. Training straight from a public Hub dataset skips this step entirely, which is why such a dataset has to arrive with those statistics already in place.
  2. Runs preflight. Validates your RunPod and Hugging Face credentials, inspects the dataset's camera keys, and resolves or creates a RunPod network volume and pod template. It prints a cost estimate for a shortlist of GPUs before anything is created.
  3. Launches the pod and provisions credentials. Your Hugging Face token (and, with --wandb, your Weights & Biases key) is pushed to the pod over SSH stdin and written as a locked-down file on the network volume, never into the pod's environment variables or console. Your RunPod API key never leaves your machine; it stays in runpodctl's own config and is never pushed to the pod or the volume.
  4. Bootstraps and starts training, detached on the pod so a dropped connection or a sleeping laptop doesn't kill the run.
  5. Monitors to completion, tailing the pod's training log for live progress and watching Hugging Face Hub for the end-of-training push. LeRobot pushes to the Hub only once, at the end of a run, so the pod log is the only live signal. On completion, the trained LoRA adapter is merged into a dense, servable checkpoint on the pod and uploaded.
  6. Tears down the pod.

Preflight Checks

Before any billing starts, preflight validates:

  • The required tools and credentials are present, and that your Hugging Face token can write the output repo.
  • The dataset exists on Hugging Face Hub and its camera keys map onto the policy's fixed base_0_rgb, left_wrist_0_rgb, and right_wrist_0_rgb slots. A wrong base/wrist mapping only shows up after a full paid run trains a policy watching the wrong camera, so preflight requires an explicit confirmation of the mapping before training; it never accepts its own guess silently.
  • A RunPod network volume and pod template exist in a datacenter that actually offers network volumes, creating them on first use.
  • Pricing for your GPU shortlist, so you see a cost estimate before committing.

What the Conversation Looks Like

Expect the skill to stop and ask you things along the way. These are based on an actual run, lightly edited for space: the first is what the assistant says before touching anything, and the second is preflight's own credential prompt, captured with a placeholder value instead of a real key:

> Use the train-model skill to fine-tune pi0.5 on my dataset lab_sim_bottle_picks,
> pushing the checkpoint to my-hf-username/bottle-picks-policy.

Before I run anything, I need: the local dataset path, the HF dataset repo id to push
the prepared data to, and, once preflight prints its candidate, which camera key is
the base view and which is the wrist view. This also spends real money once a pod
launches, so confirm your RunPod and HF credentials are ready first.
== 2/5: credentials ==
No RunPod API key configured.
Paste your RunPod API key (from https://www.runpod.io/console/user/settings): [hidden]

Nothing is sent until you paste a key here; decline with Ctrl-C to back out. Once credentials check out, preflight confirms the camera mapping and shows a cost estimate before anything is created:

== 3/5: dataset check (my-hf-username/lab-sim-bottle-picks) ==
detected camera keys:
observation.images.scene_cam
observation.images.wrist_cam
candidate RENAME_MAP (detection order -> base_0_rgb, left_wrist_0_rgb, right_wrist_0_rgb):
{"observation.images.scene_cam": "observation.images.base_0_rgb", "observation.images.wrist_cam": "observation.images.left_wrist_0_rgb"}
Use this RENAME_MAP? [Y/n, or paste a replacement JSON object]:

== 5/5: cost estimate ==
GPU preference order (indicative pricing; check the RunPod console for live rates):
NVIDIA L40S: $0.79/hr, ~8.3hr for 50000 steps -> ~$6.56
NVIDIA A100 80GB PCIe: $1.19/hr, ~8.3hr for 50000 steps -> ~$9.88

The rates in that estimate illustrate the format preflight prints rather than the price you will pay. Read the figures from your own run, and check the RunPod console for live rates.

The credential prompts are skipped when RUNPOD_API_KEY and HF_TOKEN are already exported. The camera mapping always requires an explicit confirmation, and passing --rename-map is itself that confirmation. Without that flag, the two paths differ. An interactive terminal session gets the [Y/n] prompt shown above. A non-interactive run, such as an agent driving preflight.sh through a Bash tool, instead prints the candidate mapping and exits non-zero. That non-zero exit is the expected first pass of a deliberate two-pass flow, not a failure: read the candidate, correct it if the base and wrist views are reversed, then re-run preflight passing it back through --rename-map. The typed volume-id confirmation at teardown has no such flag and stays unconditional; the skill refuses to delete the volume without the typed ID. Both mistakes are otherwise invisible until you have already paid for a full run.

Where the Checkpoint Lands

The finished checkpoint is pushed to the output model repo you named, under a merged/ folder: a dense checkpoint your MoveIt Pro robot config can serve directly, alongside the raw LoRA adapter at the repo root. See Connect a VLA Policy to the ExecutePolicy Behavior for how to point an inference server at that repo and wire it into an Objective.

Teardown and Cost Hygiene

Pods do not stop themselves. RunPod's --terminate-after flag doesn't reliably fire, so the skill always issues an explicit delete and verifies the pod is actually gone. If a run is interrupted (a crash, a closed terminal, Ctrl-C), tear the pod down by hand rather than leaving it running. The command below runs from the train-model skill's own directory, where its scripts/ live and where preflight wrote .train-model.env. A MoveIt Pro installation puts that directory at ~/moveit_pro/.claude/skills/train-model; an npx skills add install puts it under whichever skills directory you installed into:

./scripts/teardown.sh --pod-id <pod-id> --purge-secrets

Pass --purge-secrets on the same run that deletes the pod, as above. It wipes the Hugging Face token (and any Weights & Biases key) off the network volume over SSH through the still-running pod, which is the volume's only access path. Once the pod is gone, revoking the token at huggingface.co/settings/tokens is the only remaining option.

Teardown deletes the pod and nothing else. The network volume's storage charge continues indefinitely, on purpose, because its cached weights and environment make your next run cheap. Add --purge-volume once you're done training against this dataset to delete it (this also deletes anything cached on it, so your next run pays a cold start). That flag reads the volume ID from .train-model.env, so it fails with a missing-RUNPOD_VOLUME_ID error if you run teardown from a directory without that file. Pass --env-file <path> to point it at one.

Troubleshooting

  • Preflight can't validate the dataset. Confirm the repo id and that meta/info.json exists under the dataset's meta tree on Hugging Face Hub; private repos need your Hugging Face token set.
  • Pod creation fails with "no instances available." The GPU class is out of stock in that datacenter. Re-run pod creation with the next GPU in your shortlist, or a different datacenter.
  • The pod exits instantly with no logs. The training image failed to pull, which is the common failure mode for a private, auto-created image mirror. Confirm the training image is public or that the pod has registry credentials configured.
  • Monitoring exits with error: no new commit appeared on <model-name>. There are no mid-run pushes to Hugging Face, so this means the run didn't finish inside the budgeted wall-clock time, not that it never checkpointed. Check the pod's training log: a traceback means it crashed; steady progress lines mean the run needs a larger time budget.
  • The checkpoint merge fails with a base-model mismatch. The adapter was trained against a different base model than the one the skill assumes by default; pass the base model explicitly to the merge step.
  • A pod is orphaned after a crash. List and delete it by hand with runpodctl; the skill prints a recovery step on abnormal exit. The monitor script prints a recovery command, which you run as ./scripts/teardown.sh --pod-id <pod-id> from the same skill directory as the teardown command above, and a teardown that cannot verify the pod is gone points you at the RunPod console to delete it manually.

See REFERENCE.md for the full gotcha list, including SSH proxy quirks and pricing details.

Next Steps

Once a checkpoint lands on Hugging Face Hub, connect it to your robot: Connect a VLA Policy to the ExecutePolicy Behavior.