SmolVLA SFT¶
SFT of HuggingFace LeRobot's SmolVLA on the same ManiGuard joint LeRobot v2.1 datasets used for openpi and GR00T — the dataset is model-agnostic; SmolVLA only differs in how the shared cameras/state/action are presented to it.
SmolVLA is LeRobot-native: it is fine-tuned with the upstream lerobot-train
CLI and has no config registry and no embodiment registration. lerobot-train
derives the policy's input/output features straight from the dataset's standard
key prefixes (observation.images.* → visual, observation.state → state,
action → action), and the architecture is agnostic to camera count and pads the
state/action vectors to its internal width. So the ManiGuard side is the thinnest
of the three tracks: a small embodiment contract plus CLI-wrapping tools.
Why SmolVLA needs a data-prep (rename) step¶
The ManiGuard datagen export uses flat, non-standard keys (image_left,
wrist_image, state, actions, …) and ships 5 camera streams. openpi and
GR00T consume those flat keys through an indirection layer (openpi's
RepackTransform, GR00T's modality.json original_key). lerobot-train has no
such indirection — it classifies features purely by standard key prefix. So a
one-time prep step rebuilds a 2-camera, standard-keyed copy of the dataset:
| datagen (source) | → SmolVLA (standard) | note |
|---|---|---|
image_<external_cam> |
observation.images.top |
chosen overview (default left) |
wrist_image |
observation.images.wrist |
wrist |
state (8-D) |
observation.state |
absolute joint |
actions (8-D) |
action |
absolute joint target |
image_opposite / image_right / image_left_shoulder |
— | dropped |
actions_commanded |
— | dropped |
Two cameras (overview + wrist) keep parity with the pi0.5 and GR00T tracks
(identical visual input → a fair benchmark). Videos are passthrough (H.264 copied
as-is; AV1 transcoded to H.264 so LeRobot's decoder works everywhere). The mapping
is defined once in maniguard/smolvla_sft/embodiment.py (the single source of
truth) and applied by prepare_dataset.py.
Embodiment & schema¶
- Base model:
lerobot/smolvla_base(SmolVLM2 backbone + flow-matching action expert) - State / action: absolute joint 8-D (7 arm joints + 1 gripper), padded to SmolVLA's internal width; the model outputs absolute joint targets fed straight to a
JointControllerat eval (no delta transform — see end to end) - Cameras (2):
observation.images.top(overview) +observation.images.wrist - Tuning: SmolVLA default — vision encoder frozen, action expert trained (no LoRA, same "freeze VLM" strategy as the GR00T N1.6 path)
Tooling¶
| Purpose | Path |
|---|---|
| Embodiment contract (flat → standard key map, dims) | maniguard/smolvla_sft/embodiment.py |
| Dataset prep (5-cam flat → 2-cam standard-keyed copy) | tools/smolvla_sft/prepare_dataset.py |
SFT launcher (wraps lerobot-train) |
tools/smolvla_sft/run_sft.sh |
| Push checkpoint + card to HF | tools/smolvla_sft/push_to_hf.py |
| End-to-end 6-family driver | tools/smolvla_sft/run_all.sh |
Training runs against a huggingface/lerobot clone (pinned tag, installed with
the smolvla extra) in the same environment that produces the prepared dataset.
Model repos follow
<org>/smolvla-base-datagen-v1-<fam>-joint-2cam (e.g.
IDEAS-Lab-Northwestern/smolvla-base-datagen-v1-clutter-joint-2cam); run/experiment
names follow smolvla-base_datagen_v1_<fam>_joint_2cam (no _lora suffix — SmolVLA
does not use LoRA).
Running¶
One family, or all six serially (download → prepare → train → push):
export HF_TOKEN=... WANDB_API_KEY=... # both required (pre-flight checked)
bash tools/smolvla_sft/run_all.sh --family clutter
bash tools/smolvla_sft/run_all.sh --all
Each family downloads its datagen-<fam>-v1-joint-5cam dataset once into the shared
cache (MANIGUARD_SFT_DATA_ROOT, default outputs/sft_datasets/, shared with the
openpi and GR00T tracks), prepares the 2-cam copy, trains ~2 epochs, and pushes.
Steps derive from each dataset's frame count as ceil(frames × 2 / batch).
Notes¶
- LeRobot version pin. The
lerobot-trainflag surface has changed across releases (e.g.--policy.pathvs the older--policy.type). Clonehuggingface/lerobotat a pinned tag,pip install -e .[smolvla], and verify the flags inrun_sft.shagainstlerobot-train --helpfor that tag. - Compute knobs (batch size, num_workers, save frequency, LR schedule): the
defaults in
run_all.sh/run_sft.share starting points; tune for your hardware.