From c7c620533201535064cd4b84ba8c60624693c79d Mon Sep 17 00:00:00 2001 From: Steven Palma Date: Fri, 27 Feb 2026 15:26:56 +0100 Subject: [PATCH 1/9] chore(scripts): no spam log when no action (#3042) --- src/lerobot/scripts/lerobot_record.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lerobot/scripts/lerobot_record.py b/src/lerobot/scripts/lerobot_record.py index ec04975d4..661d33c51 100644 --- a/src/lerobot/scripts/lerobot_record.py +++ b/src/lerobot/scripts/lerobot_record.py @@ -333,6 +333,7 @@ def record_loop( preprocessor.reset() postprocessor.reset() + no_action_count = 0 timestamp = 0 start_episode_t = time.perf_counter() while timestamp < control_time_s: @@ -380,11 +381,13 @@ def record_loop( act = {**arm_action, **base_action} if len(base_action) > 0 else arm_action act_processed_teleop = teleop_action_processor((act, obs)) else: - logging.info( - "No policy or teleoperator provided, skipping action generation." - "This is likely to happen when resetting the environment without a teleop device." - "The robot won't be at its rest position at the start of the next episode." - ) + no_action_count += 1 + if no_action_count == 1 or no_action_count % 10 == 0: + logging.warning( + "No policy or teleoperator provided, skipping action generation. " + "This is likely to happen when resetting the environment without a teleop device. " + "The robot won't be at its rest position at the start of the next episode." + ) continue # Applies a pipeline to the action, default is IdentityProcessor From c085531b17d914fc9aea8f5c8bef0ad8497df079 Mon Sep 17 00:00:00 2001 From: Khalil Meftah Date: Fri, 27 Feb 2026 15:46:31 +0100 Subject: [PATCH 2/9] fix: add missing openarm_mini import to CLI scripts (#3028) --- src/lerobot/scripts/lerobot_calibrate.py | 1 + src/lerobot/scripts/lerobot_find_joint_limits.py | 1 + src/lerobot/scripts/lerobot_record.py | 1 + src/lerobot/scripts/lerobot_teleoperate.py | 1 + 4 files changed, 4 insertions(+) diff --git a/src/lerobot/scripts/lerobot_calibrate.py b/src/lerobot/scripts/lerobot_calibrate.py index 1b30021dd..242067978 100644 --- a/src/lerobot/scripts/lerobot_calibrate.py +++ b/src/lerobot/scripts/lerobot_calibrate.py @@ -56,6 +56,7 @@ from lerobot.teleoperators import ( # noqa: F401 make_teleoperator_from_config, omx_leader, openarm_leader, + openarm_mini, so_leader, unitree_g1, ) diff --git a/src/lerobot/scripts/lerobot_find_joint_limits.py b/src/lerobot/scripts/lerobot_find_joint_limits.py index 082d11803..bcb93ba12 100644 --- a/src/lerobot/scripts/lerobot_find_joint_limits.py +++ b/src/lerobot/scripts/lerobot_find_joint_limits.py @@ -61,6 +61,7 @@ from lerobot.teleoperators import ( # noqa: F401 make_teleoperator_from_config, omx_leader, openarm_leader, + openarm_mini, so_leader, ) from lerobot.utils.robot_utils import precise_sleep diff --git a/src/lerobot/scripts/lerobot_record.py b/src/lerobot/scripts/lerobot_record.py index 661d33c51..66e2c4228 100644 --- a/src/lerobot/scripts/lerobot_record.py +++ b/src/lerobot/scripts/lerobot_record.py @@ -125,6 +125,7 @@ from lerobot.teleoperators import ( # noqa: F401 make_teleoperator_from_config, omx_leader, openarm_leader, + openarm_mini, reachy2_teleoperator, so_leader, unitree_g1, diff --git a/src/lerobot/scripts/lerobot_teleoperate.py b/src/lerobot/scripts/lerobot_teleoperate.py index b6aa4a750..dad479b2e 100644 --- a/src/lerobot/scripts/lerobot_teleoperate.py +++ b/src/lerobot/scripts/lerobot_teleoperate.py @@ -94,6 +94,7 @@ from lerobot.teleoperators import ( # noqa: F401 make_teleoperator_from_config, omx_leader, openarm_leader, + openarm_mini, reachy2_teleoperator, so_leader, unitree_g1, From a0fdbf037ac918d0f2cdfd540db72199e0b925d0 Mon Sep 17 00:00:00 2001 From: Jade Choghari Date: Fri, 27 Feb 2026 18:58:36 +0300 Subject: [PATCH 3/9] feat(policies): add Smolvla torch compile support (#3043) * Change LIBERO init_state_id when reset. Signed-off-by: Aoqun Jin * Change LIBERO init_state_id when reset. Signed-off-by: Aoqun Jin * pre-commit run * Add torch.compile for smolvla Signed-off-by: Aoqun Jin * Add torch.compile for smolvla Add model compilation option for improved performance. Signed-off-by: Aoqun Jin * first --------- Signed-off-by: Aoqun Jin Co-authored-by: Aoqun Jin Co-authored-by: Steven Palma --- src/lerobot/policies/smolvla/configuration_smolvla.py | 3 +++ src/lerobot/policies/smolvla/modeling_smolvla.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/src/lerobot/policies/smolvla/configuration_smolvla.py b/src/lerobot/policies/smolvla/configuration_smolvla.py index c696265f2..b861b856b 100644 --- a/src/lerobot/policies/smolvla/configuration_smolvla.py +++ b/src/lerobot/policies/smolvla/configuration_smolvla.py @@ -106,6 +106,9 @@ class SmolVLAConfig(PreTrainedConfig): # Real-Time Chunking (RTC) configuration rtc_config: RTCConfig | None = None + compile_model: bool = False # Whether to use torch.compile for model optimization + compile_mode: str = "max-autotune" # Torch compile mode + def __post_init__(self): super().__post_init__() diff --git a/src/lerobot/policies/smolvla/modeling_smolvla.py b/src/lerobot/policies/smolvla/modeling_smolvla.py index 10544a949..e49226d26 100644 --- a/src/lerobot/policies/smolvla/modeling_smolvla.py +++ b/src/lerobot/policies/smolvla/modeling_smolvla.py @@ -593,6 +593,12 @@ class VLAFlowMatching(nn.Module): self.prefix_length = self.config.prefix_length self.rtc_processor = rtc_processor + # Compile model if requested + if config.compile_model: + torch.set_float32_matmul_precision("high") + self.sample_actions = torch.compile(self.sample_actions, mode=config.compile_mode) + self.forward = torch.compile(self.forward, mode=config.compile_mode) + def _rtc_enabled(self): return self.config.rtc_config is not None and self.config.rtc_config.enabled From baf9b5036586f3667c6f5310d30396b4b233a801 Mon Sep 17 00:00:00 2001 From: Khalil Meftah Date: Fri, 27 Feb 2026 17:44:53 +0100 Subject: [PATCH 4/9] Fix(diffusion): enforce no-crop behavior when crop_ratio=1.0 (#3046) * refactor(diffusion): replace crop_shape with resize_shape and crop_ratio * fix(diffusion): address review feedback on resize/crop backward compat * test: regenerate diffusion artifacts for updated default config * fix: disable crop when resize path uses crop_ratio=1.0 --------- Co-authored-by: starlitxiling <1754165401@qq.com> --- .../diffusion/configuration_diffusion.py | 44 +++++++++++++++---- .../policies/diffusion/modeling_diffusion.py | 28 ++++++++---- .../pusht_diffusion_/actions.safetensors | 2 +- .../pusht_diffusion_/grad_stats.safetensors | 2 +- .../pusht_diffusion_/output_dict.safetensors | 2 +- .../pusht_diffusion_/param_stats.safetensors | 2 +- 6 files changed, 59 insertions(+), 21 deletions(-) diff --git a/src/lerobot/policies/diffusion/configuration_diffusion.py b/src/lerobot/policies/diffusion/configuration_diffusion.py index 3d30e0941..91b3df214 100644 --- a/src/lerobot/policies/diffusion/configuration_diffusion.py +++ b/src/lerobot/policies/diffusion/configuration_diffusion.py @@ -55,10 +55,16 @@ class DiffusionConfig(PreTrainedConfig): normalization_mapping: A dictionary that maps from a str value of FeatureType (e.g., "STATE", "VISUAL") to a corresponding NormalizationMode (e.g., NormalizationMode.MIN_MAX) vision_backbone: Name of the torchvision resnet backbone to use for encoding images. - crop_shape: (H, W) shape to crop images to as a preprocessing step for the vision backbone. Must fit - within the image size. If None, no cropping is done. - crop_is_random: Whether the crop should be random at training time (it's always a center crop in eval - mode). + resize_shape: (H, W) shape to resize images to as a preprocessing step for the vision + backbone. If None, no resizing is done and the original image resolution is used. + crop_ratio: Ratio in (0, 1] used to derive the crop size from resize_shape + (crop_h = int(resize_shape[0] * crop_ratio), likewise for width). + Set to 1.0 to disable cropping. Only takes effect when resize_shape is not None. + crop_shape: (H, W) shape to crop images to. When resize_shape is set and crop_ratio < 1.0, + this is computed automatically. Can also be set directly for legacy configs that use + crop-only (without resize). If None and no derivation applies, no cropping is done. + crop_is_random: Whether the crop should be random at training time (it's always a center + crop in eval mode). pretrained_backbone_weights: Pretrained weights from torchvision to initialize the backbone. `None` means no pretrained weights. use_group_norm: Whether to replace batch normalization with group normalization in the backbone. @@ -114,7 +120,9 @@ class DiffusionConfig(PreTrainedConfig): # Architecture / modeling. # Vision backbone. vision_backbone: str = "resnet18" - crop_shape: tuple[int, int] | None = (84, 84) + resize_shape: tuple[int, int] | None = None + crop_ratio: float = 1.0 + crop_shape: tuple[int, int] | None = None crop_is_random: bool = True pretrained_backbone_weights: str | None = None use_group_norm: bool = True @@ -175,6 +183,25 @@ class DiffusionConfig(PreTrainedConfig): f"Got {self.noise_scheduler_type}." ) + if self.resize_shape is not None and ( + len(self.resize_shape) != 2 or any(d <= 0 for d in self.resize_shape) + ): + raise ValueError(f"`resize_shape` must be a pair of positive integers. Got {self.resize_shape}.") + if not (0 < self.crop_ratio <= 1.0): + raise ValueError(f"`crop_ratio` must be in (0, 1]. Got {self.crop_ratio}.") + + if self.resize_shape is not None: + if self.crop_ratio < 1.0: + self.crop_shape = ( + int(self.resize_shape[0] * self.crop_ratio), + int(self.resize_shape[1] * self.crop_ratio), + ) + else: + # Explicitly disable cropping for resize+ratio path when crop_ratio == 1.0. + self.crop_shape = None + if self.crop_shape is not None and (self.crop_shape[0] <= 0 or self.crop_shape[1] <= 0): + raise ValueError(f"`crop_shape` must have positive dimensions. Got {self.crop_shape}.") + # Check that the horizon size and U-Net downsampling is compatible. # U-Net downsamples by 2 with each stage. downsampling_factor = 2 ** len(self.down_dims) @@ -202,13 +229,12 @@ class DiffusionConfig(PreTrainedConfig): if len(self.image_features) == 0 and self.env_state_feature is None: raise ValueError("You must provide at least one image or the environment state among the inputs.") - if self.crop_shape is not None: + if self.resize_shape is None and self.crop_shape is not None: for key, image_ft in self.image_features.items(): if self.crop_shape[0] > image_ft.shape[1] or self.crop_shape[1] > image_ft.shape[2]: raise ValueError( - f"`crop_shape` should fit within the images shapes. Got {self.crop_shape} " - f"for `crop_shape` and {image_ft.shape} for " - f"`{key}`." + f"`crop_shape` should fit within the image shapes. Got {self.crop_shape} " + f"for `crop_shape` and {image_ft.shape} for `{key}`." ) # Check that all input images have the same shape. diff --git a/src/lerobot/policies/diffusion/modeling_diffusion.py b/src/lerobot/policies/diffusion/modeling_diffusion.py index 314ca369c..aa8d5dd14 100644 --- a/src/lerobot/policies/diffusion/modeling_diffusion.py +++ b/src/lerobot/policies/diffusion/modeling_diffusion.py @@ -454,12 +454,18 @@ class DiffusionRgbEncoder(nn.Module): def __init__(self, config: DiffusionConfig): super().__init__() # Set up optional preprocessing. - if config.crop_shape is not None: + if config.resize_shape is not None: + self.resize = torchvision.transforms.Resize(config.resize_shape) + else: + self.resize = None + + crop_shape = config.crop_shape + if crop_shape is not None: self.do_crop = True # Always use center crop for eval - self.center_crop = torchvision.transforms.CenterCrop(config.crop_shape) + self.center_crop = torchvision.transforms.CenterCrop(crop_shape) if config.crop_is_random: - self.maybe_random_crop = torchvision.transforms.RandomCrop(config.crop_shape) + self.maybe_random_crop = torchvision.transforms.RandomCrop(crop_shape) else: self.maybe_random_crop = self.center_crop else: @@ -485,13 +491,16 @@ class DiffusionRgbEncoder(nn.Module): # Set up pooling and final layers. # Use a dry run to get the feature map shape. - # The dummy input should take the number of image channels from `config.image_features` and it should - # use the height and width from `config.crop_shape` if it is provided, otherwise it should use the - # height and width from `config.image_features`. + # The dummy shape mirrors the runtime preprocessing order: resize -> crop. # Note: we have a check in the config class to make sure all images have the same shape. images_shape = next(iter(config.image_features.values())).shape - dummy_shape_h_w = config.crop_shape if config.crop_shape is not None else images_shape[1:] + if config.crop_shape is not None: + dummy_shape_h_w = config.crop_shape + elif config.resize_shape is not None: + dummy_shape_h_w = config.resize_shape + else: + dummy_shape_h_w = images_shape[1:] dummy_shape = (1, images_shape[0], *dummy_shape_h_w) feature_map_shape = get_output_shape(self.backbone, dummy_shape)[1:] @@ -507,7 +516,10 @@ class DiffusionRgbEncoder(nn.Module): Returns: (B, D) image feature. """ - # Preprocess: maybe crop (if it was set up in the __init__). + # Preprocess: resize if configured, then crop if configured. + + if self.resize is not None: + x = self.resize(x) if self.do_crop: if self.training: # noqa: SIM108 x = self.maybe_random_crop(x) diff --git a/tests/artifacts/policies/pusht_diffusion_/actions.safetensors b/tests/artifacts/policies/pusht_diffusion_/actions.safetensors index ef581727d..70b1411ab 100644 --- a/tests/artifacts/policies/pusht_diffusion_/actions.safetensors +++ b/tests/artifacts/policies/pusht_diffusion_/actions.safetensors @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19eaaa85f66ba4aa6388dbb83819ffad6ea4363247208f871a8dc385689f6fc8 +oid sha256:54aecbc1af72a4cd5e9261492f5e7601890517516257aacdf2a0ffb3ce281f1b size 992 diff --git a/tests/artifacts/policies/pusht_diffusion_/grad_stats.safetensors b/tests/artifacts/policies/pusht_diffusion_/grad_stats.safetensors index e00ed3238..bea7d4f19 100644 --- a/tests/artifacts/policies/pusht_diffusion_/grad_stats.safetensors +++ b/tests/artifacts/policies/pusht_diffusion_/grad_stats.safetensors @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:227296eaeeb54acdc3dae2eb8af3d4d08fb87e245337624447140b1e91cfd002 +oid sha256:88a9c3775a2aa1e90a08850521970070a4fcf0f6b82aab43cd8ccc5cf77e0013 size 47424 diff --git a/tests/artifacts/policies/pusht_diffusion_/output_dict.safetensors b/tests/artifacts/policies/pusht_diffusion_/output_dict.safetensors index f29303992..20cc4f547 100644 --- a/tests/artifacts/policies/pusht_diffusion_/output_dict.safetensors +++ b/tests/artifacts/policies/pusht_diffusion_/output_dict.safetensors @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:271b00cb2f0cd5fd26b1d53463638e3d1a6e92692ec625fcffb420ca190869e5 +oid sha256:91a2635e05a75fe187a5081504c5f35ce3417378813fa2deaf9ca4e8200e1819 size 68 diff --git a/tests/artifacts/policies/pusht_diffusion_/param_stats.safetensors b/tests/artifacts/policies/pusht_diffusion_/param_stats.safetensors index 614cc754e..365a453dd 100644 --- a/tests/artifacts/policies/pusht_diffusion_/param_stats.safetensors +++ b/tests/artifacts/policies/pusht_diffusion_/param_stats.safetensors @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:778fddbbaa64248cee35cb377c02cc2b6076f7ce5855146de677128900617ddf +oid sha256:645bff922ac7bea63ad018ebf77c303c0e4cd2c1c0dc5ef3192865281bef3dc6 size 47424 From 04de49654718c6584d1e5561506180f6196b7e71 Mon Sep 17 00:00:00 2001 From: Pepijn <138571049+pkooij@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:45:19 +0100 Subject: [PATCH 5/9] fix(logging): avoid double-counting samples across processes (#3045) --- src/lerobot/scripts/lerobot_train.py | 4 ++-- src/lerobot/utils/logging_utils.py | 6 +++-- tests/utils/test_logging_utils.py | 36 ++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/lerobot/scripts/lerobot_train.py b/src/lerobot/scripts/lerobot_train.py index 465cbf531..04d43d91e 100644 --- a/src/lerobot/scripts/lerobot_train.py +++ b/src/lerobot/scripts/lerobot_train.py @@ -380,10 +380,10 @@ def train(cfg: TrainPipelineConfig, accelerator: Accelerator | None = None): "dataloading_s": AverageMeter("data_s", ":.3f"), } - # Use effective batch size for proper epoch calculation in distributed training + # Keep global batch size for logging; MetricsTracker handles world size internally. effective_batch_size = cfg.batch_size * accelerator.num_processes train_tracker = MetricsTracker( - effective_batch_size, + cfg.batch_size, dataset.num_frames, dataset.num_episodes, train_metrics, diff --git a/src/lerobot/utils/logging_utils.py b/src/lerobot/utils/logging_utils.py index c4c1f42e0..1497c0585 100644 --- a/src/lerobot/utils/logging_utils.py +++ b/src/lerobot/utils/logging_utils.py @@ -104,9 +104,10 @@ class MetricsTracker: self.metrics = metrics self.steps = initial_step + world_size = accelerator.num_processes if accelerator else 1 # A sample is an (observation,action) pair, where observation and action # can be on multiple timestamps. In a batch, we have `batch_size` number of samples. - self.samples = self.steps * self._batch_size + self.samples = self.steps * self._batch_size * world_size self.episodes = self.samples / self._avg_samples_per_ep self.epochs = self.samples / self._num_frames self.accelerator = accelerator @@ -132,7 +133,8 @@ class MetricsTracker: Updates metrics that depend on 'step' for one step. """ self.steps += 1 - self.samples += self._batch_size * (self.accelerator.num_processes if self.accelerator else 1) + world_size = self.accelerator.num_processes if self.accelerator else 1 + self.samples += self._batch_size * world_size self.episodes = self.samples / self._avg_samples_per_ep self.epochs = self.samples / self._num_frames diff --git a/tests/utils/test_logging_utils.py b/tests/utils/test_logging_utils.py index 560ba5701..1207534c0 100644 --- a/tests/utils/test_logging_utils.py +++ b/tests/utils/test_logging_utils.py @@ -24,6 +24,11 @@ def mock_metrics(): return {"loss": AverageMeter("loss", ":.3f"), "accuracy": AverageMeter("accuracy", ":.2f")} +class MockAccelerator: + def __init__(self, num_processes: int): + self.num_processes = num_processes + + def test_average_meter_initialization(): meter = AverageMeter("loss", ":.2f") assert meter.name == "loss" @@ -82,6 +87,37 @@ def test_metrics_tracker_step(mock_metrics): assert tracker.epochs == tracker.samples / 1000 +def test_metrics_tracker_initialization_with_accelerator(mock_metrics): + tracker = MetricsTracker( + batch_size=32, + num_frames=1000, + num_episodes=50, + metrics=mock_metrics, + initial_step=10, + accelerator=MockAccelerator(num_processes=2), + ) + assert tracker.steps == 10 + assert tracker.samples == 10 * 32 * 2 + assert tracker.episodes == tracker.samples / (1000 / 50) + assert tracker.epochs == tracker.samples / 1000 + + +def test_metrics_tracker_step_with_accelerator(mock_metrics): + tracker = MetricsTracker( + batch_size=32, + num_frames=1000, + num_episodes=50, + metrics=mock_metrics, + initial_step=5, + accelerator=MockAccelerator(num_processes=2), + ) + tracker.step() + assert tracker.steps == 6 + assert tracker.samples == (5 * 32 * 2) + (32 * 2) + assert tracker.episodes == tracker.samples / (1000 / 50) + assert tracker.epochs == tracker.samples / 1000 + + def test_metrics_tracker_getattr(mock_metrics): tracker = MetricsTracker(batch_size=32, num_frames=1000, num_episodes=50, metrics=mock_metrics) assert tracker.loss == mock_metrics["loss"] From 8fff0fde7c79f23a93d845d1a50e985de01f8b8a Mon Sep 17 00:00:00 2001 From: Caroline Pascal Date: Fri, 27 Feb 2026 18:22:44 +0100 Subject: [PATCH 6/9] chore(docstrings): fixing deprecated `root` argument description in LeRobotDataset class (#3035) * chore(docstrings): fixing deprecated `root` argument docstrings in LeRobotDataset class * chore(draccus): updating draccus CLI help * chore(revert): reverting changes in lerobot_dataset_viz.py --------- Co-authored-by: Steven Palma --- examples/backward_compatibility/replay.py | 2 +- src/lerobot/configs/default.py | 2 +- src/lerobot/datasets/lerobot_dataset.py | 10 +++++----- src/lerobot/scripts/lerobot_record.py | 2 +- src/lerobot/scripts/lerobot_replay.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/backward_compatibility/replay.py b/examples/backward_compatibility/replay.py index f7c47bec5..13fdfd5f5 100644 --- a/examples/backward_compatibility/replay.py +++ b/examples/backward_compatibility/replay.py @@ -57,7 +57,7 @@ class DatasetReplayConfig: repo_id: str # Episode to replay. episode: int - # Root directory where the dataset will be stored (e.g. 'dataset/path'). + # Root directory where the dataset will be stored (e.g. 'dataset/path'). If None, defaults to $HF_LEROBOT_HOME/repo_id. root: str | Path | None = None # Limit the frames per second. By default, uses the policy fps. fps: int = 30 diff --git a/src/lerobot/configs/default.py b/src/lerobot/configs/default.py index f613b5251..dcb0cbd54 100644 --- a/src/lerobot/configs/default.py +++ b/src/lerobot/configs/default.py @@ -27,7 +27,7 @@ class DatasetConfig: # "dataset_index" into the returned item. The index mapping is made according to the order in which the # datasets are provided. repo_id: str - # Root directory where the dataset will be stored (e.g. 'dataset/path'). + # Root directory where the dataset will be stored (e.g. 'dataset/path'). If None, defaults to $HF_LEROBOT_HOME/repo_id. root: str | None = None episodes: list[int] | None = None image_transforms: ImageTransformsConfig = field(default_factory=ImageTransformsConfig) diff --git a/src/lerobot/datasets/lerobot_dataset.py b/src/lerobot/datasets/lerobot_dataset.py index bb526740e..76d44de07 100644 --- a/src/lerobot/datasets/lerobot_dataset.py +++ b/src/lerobot/datasets/lerobot_dataset.py @@ -664,11 +664,11 @@ class LeRobotDataset(torch.utils.data.Dataset): for the README). Args: - repo_id (str): This is the repo id that will be used to fetch the dataset. Locally, the dataset - will be stored under root/repo_id. - root (Path | None, optional): Local directory to use for downloading/writing files. You can also - set the HF_LEROBOT_HOME environment variable to point to a different location. Defaults to - '~/.cache/huggingface/lerobot'. + repo_id (str): This is the repo id that will be used to fetch the dataset. + root (Path | None, optional): Local directory where the dataset will be downloaded and + stored. If set, all dataset files will be stored directly under this path. If not set, the + dataset files will be stored under $HF_LEROBOT_HOME/repo_id (configurable via the + HF_LEROBOT_HOME environment variable). episodes (list[int] | None, optional): If specified, this will only load episodes specified by their episode_index in this list. Defaults to None. image_transforms (Callable | None, optional): You can pass standard v2 image transforms from diff --git a/src/lerobot/scripts/lerobot_record.py b/src/lerobot/scripts/lerobot_record.py index 66e2c4228..72708ba23 100644 --- a/src/lerobot/scripts/lerobot_record.py +++ b/src/lerobot/scripts/lerobot_record.py @@ -155,7 +155,7 @@ class DatasetRecordConfig: repo_id: str # A short but accurate description of the task performed during the recording (e.g. "Pick the Lego block and drop it in the box on the right.") single_task: str - # Root directory where the dataset will be stored (e.g. 'dataset/path'). + # Root directory where the dataset will be stored (e.g. 'dataset/path'). If None, defaults to $HF_LEROBOT_HOME/repo_id. root: str | Path | None = None # Limit the frames per second. fps: int = 30 diff --git a/src/lerobot/scripts/lerobot_replay.py b/src/lerobot/scripts/lerobot_replay.py index 8e2a394b9..7c0b5b96b 100644 --- a/src/lerobot/scripts/lerobot_replay.py +++ b/src/lerobot/scripts/lerobot_replay.py @@ -80,7 +80,7 @@ class DatasetReplayConfig: repo_id: str # Episode to replay. episode: int - # Root directory where the dataset will be stored (e.g. 'dataset/path'). + # Root directory where the dataset will be stored (e.g. 'dataset/path'). If None, defaults to $HF_LEROBOT_HOME/repo_id. root: str | Path | None = None # Limit the frames per second. By default, uses the policy fps. fps: int = 30 From 563f42bdb1db8f8a96d28d4b868c5961eefa4499 Mon Sep 17 00:00:00 2001 From: Steven Palma Date: Fri, 27 Feb 2026 19:29:35 +0100 Subject: [PATCH 7/9] chore(dependencies): Bump lerobot to 0.4.5 (#3051) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b6d85b0f6..f4fb7d249 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ discord = "https://discord.gg/s3KuuzsPFb" [project] name = "lerobot" -version = "0.4.4" +version = "0.4.5" description = "🤗 LeRobot: State-of-the-art Machine Learning for Real-World Robotics in Pytorch" dynamic = ["readme"] license = { text = "Apache-2.0" } From 095856b06af7e4bd6e79f9f741303701d052ba2d Mon Sep 17 00:00:00 2001 From: Steven Palma Date: Sat, 28 Feb 2026 14:41:28 +0100 Subject: [PATCH 8/9] chore: add AI policy (#3055) --- AI_POLICY.md | 25 +++++++++++++++++++++++++ CONTRIBUTING.md | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 AI_POLICY.md diff --git a/AI_POLICY.md b/AI_POLICY.md new file mode 100644 index 000000000..272ee8c12 --- /dev/null +++ b/AI_POLICY.md @@ -0,0 +1,25 @@ +# AI Usage Policy + +The LeRobot project welcomes contributions from everyone, and we have a few guidelines regarding AI usage to ensure high code quality, clear communication, and a healthy open-source ecosystem: + +- **Please disclose significant AI assistance.** If you used AI tools (e.g., Copilot, Claude, Cursor, ChatGPT) to generate a substantial portion of your code or text, let us know in your PR description. Transparency helps us review your changes more effectively. +- **Own your code (The Human-in-the-Loop).** You must fully understand all the changes you are proposing. If you cannot explain what your AI-assisted code does or how it interacts with LeRobot's broader architecture, please take the time to learn and test it before submitting. +- **Keep issues and discussions focused.** You are welcome to use AI to help draft issues or PR descriptions, but please review and edit them carefully before posting. AI can often be overly verbose; trimming the noise and getting straight to the point helps our maintainers address your needs faster. + +Our core maintainers also use AI tools to aid their workflows, but they do so while bringing deep contextual knowledge of the LeRobot codebase to validate the output. We ask all contributors to apply that same level of rigor. + +## Remember the Human Maintainers + +Please remember that LeRobot is maintained by a dedicated team of humans. + +Every discussion, issue, and pull request is read and reviewed by real people. While AI tools can generate thousands of lines of code in seconds, reviewing that code still takes human time and energy. Submitting unverified or low-effort AI output puts an unfair burden on our maintainers. + +Today, the quality of the AI output still heavily depends on the developer driving the tool. We ask that you respect our maintainers' time by thoroughly vetting, testing, and refining your submissions. + +## AI is Welcome Here + +LeRobot operates at the cutting edge of AI and robotics, and many of our maintainers actively embrace AI coding assistants as valuable productivity tools. We are a pro-AI project! + +Our reason for having an AI policy is not an anti-AI stance. Rather, it exists to ensure that AI is used to enhance human contributions, not replace them with unverified noise. It's about how the tools are used, not the tools themselves. + +We value the unique human insight you bring to the LeRobot community. Let AI empower your workflow, but always let your own judgment take the wheel. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c51a48831..82147d363 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Everyone is welcome to contribute, and we value everybody's contribution. Code is not the only way to help the community. Answering questions, helping others, reaching out, and improving the documentation are immensely valuable. -Whichever way you choose to contribute, please be mindful to respect our [code of conduct](./CODE_OF_CONDUCT.md). +Whichever way you choose to contribute, please be mindful to respect our [code of conduct](./CODE_OF_CONDUCT.md) and our [AI policy](./AI_POLICY.md). ## Ways to Contribute From 8bb8ed48039e9f4595b105dd99f6bfff4b9aa8e7 Mon Sep 17 00:00:00 2001 From: Bernie Telles Date: Mon, 2 Mar 2026 06:35:15 -0800 Subject: [PATCH 9/9] Improve policy_device documentation for async.mdx (#3060) --- docs/source/async.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/async.mdx b/docs/source/async.mdx index 3244fc2a3..fcc3f1d1e 100644 --- a/docs/source/async.mdx +++ b/docs/source/async.mdx @@ -48,7 +48,7 @@ python -m lerobot.async_inference.robot_client \ --task="dummy" \ # POLICY: The task to run the policy on (`Fold my t-shirt`). Not necessarily defined for all policies, such as `act` --policy_type=your_policy_type \ # POLICY: the type of policy to run (smolvla, act, etc) --pretrained_name_or_path=user/model \ # POLICY: the model name/path on server to the checkpoint to run (e.g., lerobot/smolvla_base) - --policy_device=mps \ # POLICY: the device to run the policy on, on the server + --policy_device=mps \ # POLICY: the device to run the policy on, on the server (cuda, mps, xpu, cpu) --actions_per_chunk=50 \ # POLICY: the number of actions to output at once --chunk_size_threshold=0.5 \ # CLIENT: the threshold for the chunk size before sending a new observation to the server --aggregate_fn_name=weighted_average \ # CLIENT: the function to aggregate actions on overlapping portions