|
| 1 | +# Using Subtasks in LeRobot Datasets |
| 2 | + |
| 3 | +Subtask support in robotics datasets has proven effective in improving robot reasoning and understanding. Subtasks are particularly useful for: |
| 4 | + |
| 5 | +- **Hierarchical policies**: Building policies that include subtask predictions to visualize robot reasoning in real time |
| 6 | +- **Reward modeling**: Helping reward models understand task progression (e.g., SARM-style stage-aware reward models) |
| 7 | +- **Task decomposition**: Breaking down complex manipulation tasks into atomic, interpretable steps |
| 8 | + |
| 9 | +LeRobotDataset now supports subtasks as part of its dataset structure, alongside tasks. |
| 10 | + |
| 11 | +## What are Subtasks? |
| 12 | + |
| 13 | +While a **task** describes the overall goal (e.g., "Pick up the apple and place it in the basket"), **subtasks** break down the execution into finer-grained steps: |
| 14 | + |
| 15 | +1. "Approach the apple" |
| 16 | +2. "Grasp the apple" |
| 17 | +3. "Lift the apple" |
| 18 | +4. "Move to basket" |
| 19 | +5. "Release the apple" |
| 20 | + |
| 21 | +Each frame in the dataset can be annotated with its corresponding subtask, enabling models to learn and predict these intermediate stages. |
| 22 | + |
| 23 | +<img |
| 24 | + src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/lerobot/subtask-asset.png" |
| 25 | + alt="An overview of subtask annotation showing how frames are labeled with intermediate subtask stages" |
| 26 | + width="80%" |
| 27 | +/> |
| 28 | + |
| 29 | +<p> |
| 30 | + <em>Figure: Overview of subtask annotation.</em> |
| 31 | +</p> |
| 32 | + |
| 33 | +**Reference:** _Subtask-learning based for robot self-assembly in flexible collaborative assembly in manufacturing_, Original Article, Published: 19 April 2022. |
| 34 | + |
| 35 | +## Dataset Structure |
| 36 | + |
| 37 | +Subtask information is stored in the dataset metadata: |
| 38 | + |
| 39 | +``` |
| 40 | +my-dataset/ |
| 41 | +├── data/ |
| 42 | +│ └── ... |
| 43 | +├── meta/ |
| 44 | +│ ├── info.json |
| 45 | +│ ├── stats.json |
| 46 | +│ ├── tasks.parquet |
| 47 | +│ ├── subtasks.parquet # Subtask index → subtask string mapping |
| 48 | +│ └── episodes/ |
| 49 | +│ └── ... |
| 50 | +└── videos/ |
| 51 | + └── ... |
| 52 | +``` |
| 53 | + |
| 54 | +### Subtasks Parquet File |
| 55 | + |
| 56 | +The `meta/subtasks.parquet` file maps subtask indices to their natural language descriptions: |
| 57 | + |
| 58 | +| subtask_index | subtask (index column) | |
| 59 | +| ------------- | ---------------------- | |
| 60 | +| 0 | "Approach the apple" | |
| 61 | +| 1 | "Grasp the apple" | |
| 62 | +| 2 | "Lift the apple" | |
| 63 | +| ... | ... | |
| 64 | + |
| 65 | +### Frame-Level Annotations |
| 66 | + |
| 67 | +Each frame in the dataset can include a `subtask_index` field that references the subtasks parquet file: |
| 68 | + |
| 69 | +```python |
| 70 | +# Example frame data in the parquet file |
| 71 | +{ |
| 72 | + "index": 42, |
| 73 | + "timestamp": 1.4, |
| 74 | + "episode_index": 0, |
| 75 | + "task_index": 0, |
| 76 | + "subtask_index": 2, # References "Lift the apple" |
| 77 | + "observation.state": [...], |
| 78 | + "action": [...], |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +## Annotating Datasets with Subtasks |
| 83 | + |
| 84 | +We provide a HuggingFace Space for easily annotating any LeRobotDataset with subtasks: |
| 85 | + |
| 86 | +**[https://huggingface.co/spaces/lerobot/annotate](https://huggingface.co/spaces/lerobot/annotate)** |
| 87 | + |
| 88 | +After completing your annotation: |
| 89 | + |
| 90 | +1. Click "Push to Hub" to upload your annotated dataset |
| 91 | +2. You can also run the annotation space locally by following the instructions at [github.com/huggingface/lerobot-annotate](https://github.com/huggingface/lerobot-annotate) |
| 92 | + |
| 93 | +## Loading Datasets with Subtasks |
| 94 | + |
| 95 | +When you load a dataset with subtask annotations, the subtask information is automatically available: |
| 96 | + |
| 97 | +```python |
| 98 | +from lerobot.datasets.lerobot_dataset import LeRobotDataset |
| 99 | + |
| 100 | +# Load a dataset with subtask annotations |
| 101 | +dataset = LeRobotDataset("jadechoghari/collect-fruit-annotated") |
| 102 | + |
| 103 | +# Access a sample |
| 104 | +sample = dataset[100] |
| 105 | + |
| 106 | +# The sample includes both task and subtask information |
| 107 | +print(sample["task"]) # "Collect the fruit" |
| 108 | +print(sample["subtask"]) # "Grasp the apple" |
| 109 | +print(sample["task_index"]) # tensor(0) |
| 110 | +print(sample["subtask_index"]) # tensor(2) |
| 111 | +``` |
| 112 | + |
| 113 | +### Checking for Subtask Support |
| 114 | + |
| 115 | +You can check if a dataset has subtask annotations: |
| 116 | + |
| 117 | +```python |
| 118 | +# Check if subtasks are available |
| 119 | +has_subtasks = ( |
| 120 | + "subtask_index" in dataset.features |
| 121 | + and dataset.meta.subtasks is not None |
| 122 | +) |
| 123 | + |
| 124 | +if has_subtasks: |
| 125 | + print(f"Dataset has {len(dataset.meta.subtasks)} unique subtasks") |
| 126 | + print("Subtasks:", list(dataset.meta.subtasks.index)) |
| 127 | +``` |
| 128 | + |
| 129 | +## Using Subtasks for Training |
| 130 | + |
| 131 | +### With the Tokenizer Processor |
| 132 | + |
| 133 | +The `TokenizerProcessor` automatically handles subtask tokenization for Vision-Language Action (VLA) models: |
| 134 | + |
| 135 | +```python |
| 136 | +from lerobot.processor.tokenizer_processor import TokenizerProcessor |
| 137 | +from lerobot.processor.pipeline import ProcessorPipeline |
| 138 | + |
| 139 | +# Create a tokenizer processor |
| 140 | +tokenizer_processor = TokenizerProcessor( |
| 141 | + tokenizer_name_or_path="google/paligemma-3b-pt-224", |
| 142 | + padding="max_length", |
| 143 | + max_length=64, |
| 144 | +) |
| 145 | + |
| 146 | +# The processor will automatically tokenize subtasks if present in the batch |
| 147 | +# and add them to the observation under: |
| 148 | +# - "observation.subtask.tokens" |
| 149 | +# - "observation.subtask.attention_mask" |
| 150 | +``` |
| 151 | + |
| 152 | +When subtasks are available in the batch, the tokenizer processor adds: |
| 153 | + |
| 154 | +- `observation.subtask.tokens`: Tokenized subtask text |
| 155 | +- `observation.subtask.attention_mask`: Attention mask for the subtask tokens |
| 156 | + |
| 157 | +### DataLoader with Subtasks |
| 158 | + |
| 159 | +```python |
| 160 | +import torch |
| 161 | +from lerobot.datasets.lerobot_dataset import LeRobotDataset |
| 162 | + |
| 163 | +dataset = LeRobotDataset("jadechoghari/collect-fruit-annotated") |
| 164 | + |
| 165 | +dataloader = torch.utils.data.DataLoader( |
| 166 | + dataset, |
| 167 | + batch_size=16, |
| 168 | + shuffle=True, |
| 169 | +) |
| 170 | + |
| 171 | +for batch in dataloader: |
| 172 | + # Access subtask information in the batch |
| 173 | + subtasks = batch["subtask"] # List of subtask strings |
| 174 | + subtask_indices = batch["subtask_index"] # Tensor of subtask indices |
| 175 | + |
| 176 | + # Use for training hierarchical policies or reward models |
| 177 | + print(f"Batch subtasks: {set(subtasks)}") |
| 178 | +``` |
| 179 | + |
| 180 | +## Example Datasets with Subtask Annotations |
| 181 | + |
| 182 | +Try loading a dataset with subtask annotations: |
| 183 | + |
| 184 | +```python |
| 185 | +from lerobot.datasets.lerobot_dataset import LeRobotDataset |
| 186 | + |
| 187 | +# Example dataset with subtask annotations |
| 188 | +dataset = LeRobotDataset("jadechoghari/collect-fruit-annotated") |
| 189 | + |
| 190 | +# Explore the subtasks |
| 191 | +print("Available subtasks:") |
| 192 | +for subtask_name in dataset.meta.subtasks.index: |
| 193 | + print(f" - {subtask_name}") |
| 194 | + |
| 195 | +# Get subtask distribution |
| 196 | +subtask_counts = {} |
| 197 | +for i in range(len(dataset)): |
| 198 | + sample = dataset[i] |
| 199 | + subtask = sample["subtask"] |
| 200 | + subtask_counts[subtask] = subtask_counts.get(subtask, 0) + 1 |
| 201 | + |
| 202 | +print("\nSubtask distribution:") |
| 203 | +for subtask, count in sorted(subtask_counts.items(), key=lambda x: -x[1]): |
| 204 | + print(f" {subtask}: {count} frames") |
| 205 | +``` |
| 206 | + |
| 207 | +## Use Cases |
| 208 | + |
| 209 | +### 1. Hierarchical Policy Training |
| 210 | + |
| 211 | +Train policies that predict both actions and current subtask: |
| 212 | + |
| 213 | +```python |
| 214 | +class HierarchicalPolicy(nn.Module): |
| 215 | + def __init__(self, num_subtasks): |
| 216 | + super().__init__() |
| 217 | + self.action_head = nn.Linear(hidden_dim, action_dim) |
| 218 | + self.subtask_head = nn.Linear(hidden_dim, num_subtasks) |
| 219 | + |
| 220 | + def forward(self, observations): |
| 221 | + features = self.encoder(observations) |
| 222 | + actions = self.action_head(features) |
| 223 | + subtask_logits = self.subtask_head(features) |
| 224 | + return actions, subtask_logits |
| 225 | +``` |
| 226 | + |
| 227 | +### 2. Stage-Aware Reward Modeling (SARM) |
| 228 | + |
| 229 | +Build reward models that understand task progression: |
| 230 | + |
| 231 | +```python |
| 232 | +# SARM predicts: |
| 233 | +# - Stage: Which subtask is being executed (discrete) |
| 234 | +# - Progress: How far along the subtask (continuous 0-1) |
| 235 | + |
| 236 | +class SARMRewardModel(nn.Module): |
| 237 | + def forward(self, observations): |
| 238 | + features = self.encoder(observations) |
| 239 | + stage_logits = self.stage_classifier(features) |
| 240 | + progress = self.progress_regressor(features) |
| 241 | + return stage_logits, progress |
| 242 | +``` |
| 243 | + |
| 244 | +### 3. Progress Visualization |
| 245 | + |
| 246 | +Monitor robot execution by tracking subtask progression: |
| 247 | + |
| 248 | +```python |
| 249 | +def visualize_execution(model, observations): |
| 250 | + for t, obs in enumerate(observations): |
| 251 | + action, subtask_logits = model(obs) |
| 252 | + predicted_subtask = subtask_names[subtask_logits.argmax()] |
| 253 | + print(f"t={t}: Executing '{predicted_subtask}'") |
| 254 | +``` |
| 255 | + |
| 256 | +## API Reference |
| 257 | + |
| 258 | +### LeRobotDataset Properties |
| 259 | + |
| 260 | +| Property | Type | Description | |
| 261 | +| --------------------------- | ---------------------- | ------------------------------------------ | |
| 262 | +| `meta.subtasks` | `pd.DataFrame \| None` | DataFrame mapping subtask names to indices | |
| 263 | +| `features["subtask_index"]` | `dict` | Feature spec for subtask_index if present | |
| 264 | + |
| 265 | +### Sample Keys |
| 266 | + |
| 267 | +When subtasks are available, each sample includes: |
| 268 | + |
| 269 | +| Key | Type | Description | |
| 270 | +| --------------- | -------------- | ------------------------------------ | |
| 271 | +| `subtask_index` | `torch.Tensor` | Integer index of the current subtask | |
| 272 | +| `subtask` | `str` | Natural language subtask description | |
| 273 | + |
| 274 | +## Related Resources |
| 275 | + |
| 276 | +- [SARM Paper](https://arxiv.org/pdf/2509.25358) - Stage-Aware Reward Modeling for Long Horizon Robot Manipulation |
| 277 | +- [LeRobot Annotate Space](https://huggingface.co/spaces/lerobot/annotate) - Interactive annotation tool |
| 278 | +- [LeRobotDataset v3.0](./lerobot-dataset-v3) - Dataset format documentation |
0 commit comments