Offsiteteam
Robotics
July 16, 2026

Robotics data formats: LeRobot and NVIDIA GR00T

There is no single universal robotics data format yet, but several formats and standards are de facto widely used in robotics research and industry.

Among them

  • LeRobot
  • Isaac / NVIDIA - GR00T Project
  • RLDS (Reinforcement Learning Dataset Standard)
  • ROS Bag (raw robotics logging format)

Let’s start our exploration from LeRobot v2 format.

 

LeRobot v2.0 format in detail

It is used in one of the most widely known LeRobot ML robotics frameworks from Hugging Face. It is reused to some extent by many other frameworks, including those from Physical Intelligence, etc.

Let’s explore it using an example of the ALOHA robotics setup dataset from here.

Note, that originally ALOHA was not created in LeRobot formats, it is more like a raw telemetry log, but here it is converted into LeRobot.

LeRobot image

LeRobot v2.0 dataset is a folder with the following structure:

.
├── data
│   └── chunk-000
│       ├── episode_000000.parquet
│       ├── ...
│       └── episode_000049.parquet
├── meta
│   ├── episodes_stats.jsonl
│   ├── episodes.jsonl
│   ├── info.json
│   └── tasks.jsonl
└── videos
    └── chunk-000
        ├── observation.images.cam_high
        │   ├── episode_000000.mp4
        │   ├── ...
        │   └── episode_000049.mp4
        ├── observation.images.cam_left_wrist
        │   ├── episode_000000.mp4
        │   ├── ...
        │   └── episode_000049.mp4
        └── observation.images.cam_right_wrist
            ├── episode_000000.mp4
            ├── ...
            └── episode_000049.mp4

The structure is mostly self-explanatory and contains three subfolders: data, meta, and videos.

  • The data folder contains robot state and action data in Parquet format.
  • The meta folder describes what fields are stored in the data folder. In particular, info.json (via its features field) defines exactly what is stored in the Parquet files.
  • The videos folder stores MPEG/MP4 video recordings from each available camera.

Meta folder

The meta folder provides a detailed description of which data is stored in the dataset.

For example, info.json contains a description of the dataset features, i.e. what is collected and how it is structured:

As a side note, let me emphasize that the term episodes here is closely related to the concept of episodic memory discussed in LeCun’s article “Why AI systems don’t learn and what to do about it”.

Info.json:

"data_path": "data/chunk-{episode_chunk:03d}/episode_{episode_index:06d}.parquet",
"video_path": "videos/chunk-{episode_chunk:03d}/{video_key}/episode_{episode_index:06d}.mp4",
"features": {
    "observation.images.cam_high": dtype=video,
    "observation.images.cam_left_wrist": dtype=video,
    "observation.images.cam_low": dtype=video,
    "observation.images.cam_right_wrist": dtype=video,
    "observation.state": dtype=float32,

    "action": dtype=float32,
    "episode_index": dtype=int64,
    "frame_index": dtype=int64,
    "timestamp": dtype=float32,
    "next.done": dtype=bool,
    "index": dtype=int64,
    "task_index": dtype=int64
}

The data_path and video_path fields define where episode data and video recordings are stored within the dataset directory structure.

"data_path": "data/chunk-{episode_chunk:03d}/episode_{episode_index:06d}.parquet",
"video_path": "videos/chunk-{episode_chunk:03d}/{video_key}/episode_{episode_index:06d}.mp4"

Videos folder

The videos folder is used for storing video recordings of episodes in MP4 format. Each episode is typically synchronized with one or more camera views (e.g., wrist, head, or external cameras).

Data folder

The data folder contains all other numerical and sensor information, such as robot joint states, actions, timestamps, and episode metadata. This information is stored in Parquet format and structured as a time series.

Example row structure:

observation.state		action				episode_index	frame_index	timestamp	next.done	index	task_index
[-0.0015339808, -0.96334...	[-0.013805827, -0.9526021...	0		0		0.00		False		0	0

Here is s brief explanation of each filed in the parquet

observation.state

This keeps the trajectory of the robot state, i.e. the time series of all joint angles at each time point, so in the observation.state column, we can see these numbers:

[-0.00153398, -0.96334   ,  1.1734953 ,  0.00153398, -0.2960583 ,
-0.00153398, -0.04903939, -0.00460194, -0.96334   ,  1.1734953 ,
-0.00153398, -0.2960583 , -0.00153398, -0.0205711 ]

The meaning of those numbers is explained in the names section of info.json. So basically, in our case of the two-arm ALOHA robot, we only have servo motor angles, which completely define the robot state:

"names": {
    "motors": [
        "left_waist",
        "left_shoulder",
        "left_elbow",
        "left_forearm_roll",
        "left_wrist_angle",
        "left_wrist_rotate",
        "left_gripper",
        "right_waist",
        "right_shoulder",
        "right_elbow",
        "right_forearm_roll",
        "right_wrist_angle",
        "right_wrist_rotate",
        "right_gripper"
    ]
}

These are joint angles (positions) in radians of each servo motor in the current robotic setup. In our example, since this is an ALOHA bimanual robot dataset, we have two arms, left and right, with corresponding motors.

Actions

Actions here are commanded joint positions — meaning the controller instructs the motors to move to these target angles.

[-0.01380583, -0.9526021 ,  1.1688933 , -0.00153398, -0.30679616,
 -0.00153398, -0.12270375, -0.0076699 , -0.960272  ,  1.1627575 ,
 -0.00306796, -0.30526218, -0.00153398, -0.15765645]

So, in other words, given the current joint angles in observation.state, the robot is commanded to transition to the next joint position specified in the action row.

observation.state_t   (current joint angles)
action_t              (desired joint angles)

# at next time t+1
observation.state_t_next   (next joint angles)

task_index

Note that VLA is different from imitation learning in that it also includes a language command. This is where task_index comes into play. We can see that in our data it is always zero. It is actually an index of the language command, and we can find the corresponding command in tasks.jsonl:

{"task_index": 0, "task": "Pick up the plastic cup and open its lid."}

Now we can see that LeRobot also supports VLA training.

Other fields

  • episode_index – Which episode (trajectory) the sample belongs to. This number corresponds to the episode_000X.parquet file.
  • frame_index – The timestep within the episode.
  • index – A global dataset index that enumerates rows across the entire dataset, regardless of episode.
  • next.done – This is very important, especially for RL. It indicates whether the episode terminates at the next timestep. If we are training RL, it is important to know whether the next state t + 1 is terminal (see the next paragraph).

The improvement in LeRobot v3.0 data format

The differences between v2.0 and v3.0 are mostly cosmetic, and their purpose is primarily optimization for large-scale datasets.

Briefly

  • v2.0 is episode-centric storage — each episode is stored separately. It is easy to reason about, but harder to scale.
  • v3.0 is columnar + chunked storage (chunk-centered)
    • data is batched across episodes (each chunk contains many episodes stored in large Parquet files). It is optimized for fast loading and streaming, and each chunk is independently loadable.
    • Video for an entire chunk is also combined into a single MP4 file, with indexing metadata stored in the meta folder.

Purpose of such chunking is

  • Parallel data loading
  • Distributed training
  • Streaming datasets

Also, in modern training setups, you do not train episode-by-episode anymore. Instead, you sample random frames or short sequences. Because of this, chunking across multiple episodes is a more suitable data organization.

So, overall the LeRobot v.30 folder structure is as follows:

.
├── data
│   └── chunk-000
│       └── file-000.parquet
├── meta
│   ├── episodes
│   │   └── chunk-000
│   │       └── file-000.parquet
│   ├── info.json
│   ├── stats.json
│   └── tasks.parquet
├── README.md
└── videos
    ├── observation.images.cam_high
    │   └── chunk-000
    │       └── file-000.mp4
    ├── observation.images.cam_left_wrist
    │   └── chunk-000
    │       └── file-000.mp4
    ├── observation.images.cam_low
    │   └── chunk-000
    │       └── file-000.mp4
    └── observation.images.cam_right_wrist
        └── chunk-000
            └── file-000.mp4

Note, that there is also an additional episode-level metadata structure (meta/episodes), because episodes are no longer stored as individual Parquet files. Instead, multiple episodes are packed into a single chunked Parquet file, so explicit indexing is required to map each row back to its corresponding episode.

Difference between datasets for Imitation Learning, VLA and RL training

The structure described above is ideal for imitation learning because, in imitation learning, the agent reacts at every time step.

Each time step provides a training example, and we can train the policy as follows:

function to learn: observation_t → action_t
loss = || predicted_action - action_t ||

In imitation learning, we do not need a reward signal and do not have to wait until the end of the episode. However, we do need teleoperation or expert demonstration data. This type of dataset is well-suited for simple policies like ACT or VLA. VLA models are also typically trained in an imitation learning setup, with the addition of a language command associated with the entire episode.

For the RL situation is a bit different: we need the data of the following structure

(state_t, action_t, reward_t, state_{t+1})

The key addition here is the reward signal. In RL, we do not have access to expert demonstrations that define the correct action at each step. So instead of asking “what is the correct action?”, we ask: “How good was this action based on what happened next?”

(state_t, action_t) → (reward_t, state_{t+1})

So our reward is essentially a proxy for “how good was that step for the overall task?”

One complication is that the reward is not something you “get for free”; you have to design or measure it from sensors, environment state, or task success signals or we can artificially calculate reward by one of the following technics.

For example it might be distance-based reward - by computing distance between end-effector and target Reward or something like that. Or Reward also might be computed from images or even with only final reward, RL can propagate it backward.


NVIDIA GR00T data format

Let’s explore it using an example of the cube_to_bowl_5 dataset for a single robotic arm.

It is very similar to LeRobot v2.0, with several distinctions. We can think of it as GR00T LeRobot, as described here.

 

The main difference compared to LeRobot 2.0 is the introduction of a new metadata file:

NVIDIA GR00T image
modality.json # -> GR00T LeRobot specific

This file provides detailed metadata about state and action modalities.

GROOT dataset looks like the following:

.
├── data
│   └── chunk-000
│       ├── episode_000000.parquet
│       ├── ...
│       └── episode_000004.parquet
├── meta
│   ├── episodes.jsonl
│   ├── info.json
│   ├── modality.json
│   ├── relative_stats.json
│   ├── stats.json
│   └── tasks.jsonl
└── videos
    └── chunk-000
        ├── observation.images.front
        │   ├── episode_000000.mp4
        │   ├── ...
        │   └── episode_000004.mp4
        └── observation.images.wrist
            ├── episode_000000.mp4
            ├── ...
            └── episode_000004.mp4

The most important addition are:

  • additional modality.json
  • In addition, instead of episodes_stats.jsonl (used in LeRobot 2.0), GR00T introduces:
    • episodes.jsonl
    • realative_stats.jsonl
    • stats.jsonl

The change in statistics files is mostly cosmetic. In general, statistics provide aggregated values (min, max, mean, etc.) for each joint dimension or feature across time.

The modality.json file defines how raw data should be interpreted by the training pipeline.

It tells the training pipeline what signals exist, how they are structured, and how to interpret raw data fields.

In plain words it tells the training pipeline the structure of your robotics system: how many arms, with how many degrees of freedom, how many cameras etc.

{
    "state": {
        "single_arm": {
            "start": 0,
            "end": 5
        },
        "gripper": {
            "start": 5,
            "end": 6
        }
    },
    "action": {
        "single_arm": {
            "start": 0,
            "end": 5
        },
        "gripper": {
            "start": 5,
            "end": 6
        }
    },
    "video": {
        "front": {
            "original_key": "observation.images.front"
        },
        "wrist": {
            "original_key": "observation.images.wrist"
        }
    },
    "annotation": {
        "human.task_description": {
            "original_key": "task_index"
        }
    }
}

Conclusion

LeRobot v2.0 provides a clean and interpretable representation of robotic trajectories, while v3.0 improves scalability through chunked storage and streaming-oriented design.

GR00T extends this idea further by explicitly introducing modality-level definitions that make datasets more portable across different robot embodiments and sensing setups.

Ready to use Robotics
to improve your business?
Fill out the form below to tell us about your project.
We'll contact you promptly to discuss your needs.
We received your message!
Thank you!