The SO-100 and the LeRobot dataset format

LeRobot is the de facto interchange format for low-cost manipulation data. If your SO-100 recordings are in it, most of the open-source training ecosystem will accept them. This page covers the structure, the version split, and the one compatibility trap that costs people an afternoon.

Last updated 2026-08-09

Why the format matters more than it sounds

Every research group used to invent its own recording layout, which meant every dataset needed a bespoke loader. LeRobot, maintained by Hugging Face, ended most of that. Recording into it means you can hand your data to a policy trainer you did not write, and it will load.

What the layout looks like

A LeRobot dataset is a directory of Parquet files for the numeric streams, encoded video for the camera streams, and JSON metadata describing what the columns mean. A fresh recording is not quite that shape yet. The Parquet files and the meta/ files are there from the first saved episode, but each camera keeps a directory of numbered JPEGs per episode instead of a video, and there is no stats.json. The export step re-encodes each of those directories into one MP4 per episode and computes the per-feature statistics used to scale the data, which is what produces the layout below. There is exactly one place that starts it: the Upload step at the end of the recording wizard in the desktop client, the step that offers to push the episode to a Hugging Face repository. It is not a terminal command, and the dataset pages in the dashboard do not offer it.

text
my_so100_pick/
β”œβ”€β”€ meta/
β”‚   β”œβ”€β”€ info.json          # fps, robot type, feature schema
β”‚   β”œβ”€β”€ episodes.jsonl     # one line per episode, with its task string
β”‚   β”œβ”€β”€ tasks.jsonl        # the task strings, indexed
β”‚   └── stats.json         # per-feature mean/std, used for normalisation
β”œβ”€β”€ data/
β”‚   └── chunk-000/
β”‚       └── episode_000000.parquet   # joint states + actions per frame
└── videos/
    └── chunk-000/
        β”œβ”€β”€ observation.images.wrist/episode_000000.mp4
        └── observation.images.scene/episode_000000.mp4
An SO-100 dataset after the export step. This is the layout a trainer expects.

Uploading does not convert anything, it packs the recording folder as it stands, which is why you can find JPEG directories inside a dataset that already reached the platform. Exporting first does not change that. The export leaves the recording untouched and writes its converted copy into a separate exports directory, and the upload keeps reading the recording. They also point at different places: the export runs behind the Upload step at the end of the recording wizard in the desktop client, so it is the route to a Hugging Face repository rather than to the copy on the platform, while ay-robots datasets upload is the route to the platform and leaves Hugging Face out of it.

Version 2.x versus 3.0

LeRobot v3.0 reorganised how episodes are chunked and how metadata is stored, mainly to handle datasets far larger than a single workstation records. It is the better format going forward. The catch is that the training ecosystem moved more slowly than the format did.

Recording here stamps v2.0, so codebase_version in meta/info.json reads v2.0 on a dataset you made yourself. That changes nothing on this page. v2.0 and v2.1 are two releases of the same layout, both load, and the split that decides whether a run starts is v2.x against v3.0.

v2.0 and v2.1v3.0
Episode filesOne Parquet file per episodeEpisodes batched into larger chunks
Metadataepisodes.jsonlRestructured, with additional index files
Large datasetsSlows down past a few thousand episodesDesigned for it
Trainer supportAccepted nearly everywhereStill patchy in third-party trainers
GR00T does not load v3.0

NVIDIA GR00T reads the v2.x layout and gives up on v3.0. A dataset recorded here is v2.x already, so there is nothing for you to switch. The one to check is a dataset that came from somewhere else: read codebase_version in meta/info.json before you queue the run. This is the single most common reason a training job fails immediately after upload, and the error message is not obvious about the cause.

The platform reads both versions for dataset preview and archiving, so you can inspect either. The constraint is on the training side, not on ingest.

Moving datasets between your machine and the platform

Recording writes into the local datasets directory, one folder per dataset. The CLI moves those folders to the platform and back, but the two directions do not take the same kind of id, and that is where people get stuck. Upload takes the local id: the id that datasets list --local prints, which is also the folder name. Everything outside letters, digits, underscore and hyphen is replaced before the backend looks that folder up, so a path such as ./my_so100_pick never resolves to anything. Download takes the id the platform gave the dataset when it arrived there, which you read from datasets list without --local. A local id will not match anything in the cloud listing, and a cloud id is not a folder name.

bash
# What is on this machine
ay-robots datasets list --local

# Push one up, by its local id
ay-robots datasets upload my_so100_pick

# What is on the platform, with the ids the platform assigned
ay-robots datasets list

# Pull one back down, by its cloud id
ay-robots datasets download <cloud-dataset-id>

A pull does not restore the folder it came from. It names the new local folder after the cloud dataset name, with the same replacement of everything outside letters, digits, underscore and hyphen, and appends a numbered suffix when that name is taken. Pulling back something you pushed from this machine therefore lands next to the original as my_so100_pick-2 instead of overwriting it.

Frequently asked questions

Which version should I record in?β–Ύ

You do not choose. Recording stamps v2.0 and there is no version switch to find. That is the layout GR00T trains directly; ACT, SmolVLA and Pi0.5 run on lerobot 0.5.1, which reads only v3.0, so those three need a one-time converted copy. The scaling advantages of v3.0 only start to matter well past the dataset size a single SO-100 produces.

Can I convert v3.0 back to v2.1?β–Ύ

Not with the LeRobot tooling. It ships only the upward path, v2.1 to v3.0. The way back is tools/v30_to_groot_v21.py in the training server, which writes the GR00T flavored v2.1 layout. Expect a few minutes for a mid-sized dataset, since the video files are re-encoded per episode.

Does my dataset have to be public on the Hugging Face Hub?β–Ύ

No. The format and the hub are separate things. Datasets recorded with the client stay in your account unless you publish them yourself.

Was this page helpful?