mirror of
https://github.com/huggingface/lerobot.git
synced 2026-05-14 16:19:45 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ec9392bcb | |||
| 84b34ae75c | |||
| ff267c772b | |||
| 652b1b854d | |||
| 8831b3c47b |
+77
-3
@@ -55,7 +55,8 @@ To make your environment loadable from the Hub, your repository must contain at
|
|||||||
|
|
||||||
**`env.py`** (or custom Python file)
|
**`env.py`** (or custom Python file)
|
||||||
|
|
||||||
- Must expose a `make_env(n_envs: int, use_async_envs: bool)` function
|
- Must expose a `make_env(n_envs: int, use_async_envs: bool, **kwargs)` function
|
||||||
|
- The function should accept `**kwargs` to allow users to pass custom configurations
|
||||||
- This function should return one of:
|
- This function should return one of:
|
||||||
- A `gym.vector.VectorEnv` (most common)
|
- A `gym.vector.VectorEnv` (most common)
|
||||||
- A single `gym.Env` (will be automatically wrapped)
|
- A single `gym.Env` (will be automatically wrapped)
|
||||||
@@ -99,6 +100,8 @@ Create an `env.py` file with a `make_env` function:
|
|||||||
```python
|
```python
|
||||||
# env.py
|
# env.py
|
||||||
import gymnasium as gym
|
import gymnasium as gym
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
def make_env(n_envs: int = 1, use_async_envs: bool = False):
|
def make_env(n_envs: int = 1, use_async_envs: bool = False):
|
||||||
"""
|
"""
|
||||||
@@ -250,6 +253,76 @@ envs_dict = make_env(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Custom Configuration via kwargs
|
||||||
|
|
||||||
|
Hub environments can accept custom configurations through keyword arguments. This is useful for parameterizing tasks, loading different objects, or overriding default settings:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Pass a config file path
|
||||||
|
envs_dict = make_env(
|
||||||
|
"nvkartik/isaaclab-arena-envs:envs/microwave_g1.py",
|
||||||
|
n_envs=4,
|
||||||
|
trust_remote_code=True,
|
||||||
|
config_path=Path("/path/to/my_config.yaml"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pass config overrides as a dictionary
|
||||||
|
envs_dict = make_env(
|
||||||
|
"nvkartik/isaaclab-arena-envs:envs/microwave_g1.py",
|
||||||
|
n_envs=4,
|
||||||
|
trust_remote_code=True,
|
||||||
|
config_overrides={
|
||||||
|
"scene.object": "microwave",
|
||||||
|
"sim.dt": 0.01,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Combine config path with overrides
|
||||||
|
envs_dict = make_env(
|
||||||
|
"username/my-env",
|
||||||
|
n_envs=4,
|
||||||
|
trust_remote_code=True,
|
||||||
|
config_path="configs/gr1_pick_place.yaml",
|
||||||
|
config_overrides={"scene.table_objects": ["apple", "banana", "cup"]},
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Any keyword arguments you pass will be forwarded to the hub environment's `make_env` function. Check the environment's documentation for supported configuration options.
|
||||||
|
|
||||||
|
### Using Custom kwargs with lerobot-eval
|
||||||
|
|
||||||
|
When evaluating policies using the `lerobot-eval` CLI, you can pass custom kwargs to hub environments using the `--env_kwargs.` prefix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lerobot-eval \
|
||||||
|
--policy.path=user123/example-policy-checkpoint \
|
||||||
|
--env=user123/example-sim-backend \
|
||||||
|
--eval.batch_size=1 \
|
||||||
|
--eval.n_episodes=10 \
|
||||||
|
--env_kwargs.task_id=demo_task_alpha \
|
||||||
|
--env_kwargs.agent_profile=arm_v2 \
|
||||||
|
--env_kwargs.target_item=object_red \
|
||||||
|
--env_kwargs.run_mode=offscreen \
|
||||||
|
--env_kwargs.enable_sensors=true \
|
||||||
|
--env_kwargs.record_output=true \
|
||||||
|
--env_kwargs.output_horizon=10 \
|
||||||
|
--env_kwargs.output_stride=15 \
|
||||||
|
--env_kwargs.state_features=joint_angles \
|
||||||
|
--env_kwargs.visual_streams=front_camera
|
||||||
|
```
|
||||||
|
|
||||||
|
All `--env_kwargs.*` arguments will be collected into a dictionary and passed as keyword arguments to the hub environment's `make_env` function. This allows you to:
|
||||||
|
|
||||||
|
- Pass configuration file paths
|
||||||
|
- Override default settings
|
||||||
|
- Specify custom task parameters
|
||||||
|
- Control simulation options (headless mode, camera settings, etc.)
|
||||||
|
- Select different embodiments or objects
|
||||||
|
|
||||||
|
The hub environment's `make_env` function receives these as regular keyword arguments, so check the environment's documentation for the available options.
|
||||||
|
|
||||||
## URL Format Reference
|
## URL Format Reference
|
||||||
|
|
||||||
The hub URL format supports several patterns:
|
The hub URL format supports several patterns:
|
||||||
@@ -266,7 +339,7 @@ The hub URL format supports several patterns:
|
|||||||
For benchmarks with multiple tasks (like LIBERO), return a nested dictionary:
|
For benchmarks with multiple tasks (like LIBERO), return a nested dictionary:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def make_env(n_envs: int = 1, use_async_envs: bool = False):
|
def make_env(n_envs: int = 1, use_async_envs: bool = False, **kwargs):
|
||||||
env_cls = gym.vector.AsyncVectorEnv if use_async_envs else gym.vector.SyncVectorEnv
|
env_cls = gym.vector.AsyncVectorEnv if use_async_envs else gym.vector.SyncVectorEnv
|
||||||
|
|
||||||
# Return dict: {suite_name: {task_id: VectorEnv}}
|
# Return dict: {suite_name: {task_id: VectorEnv}}
|
||||||
@@ -388,8 +461,9 @@ pip install gymnasium numpy
|
|||||||
Your `env.py` must expose a `make_env` function:
|
Your `env.py` must expose a `make_env` function:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def make_env(n_envs: int, use_async_envs: bool):
|
def make_env(n_envs: int, use_async_envs: bool, **kwargs):
|
||||||
# Your implementation
|
# Your implementation
|
||||||
|
# kwargs can include config_path, config_overrides, etc.
|
||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ class EvalPipelineConfig:
|
|||||||
seed: int | None = 1000
|
seed: int | None = 1000
|
||||||
# Rename map for the observation to override the image and state keys
|
# Rename map for the observation to override the image and state keys
|
||||||
rename_map: dict[str, str] = field(default_factory=dict)
|
rename_map: dict[str, str] = field(default_factory=dict)
|
||||||
|
# Additional kwargs to pass to hub environments (e.g., config_path, config_overrides, custom params)
|
||||||
|
env_kwargs: dict = field(default_factory=dict)
|
||||||
# Explicit consent to execute remote code from the Hub (required for hub environments).
|
# Explicit consent to execute remote code from the Hub (required for hub environments).
|
||||||
trust_remote_code: bool = False
|
trust_remote_code: bool = False
|
||||||
|
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ def make_env(
|
|||||||
use_async_envs: bool = False,
|
use_async_envs: bool = False,
|
||||||
hub_cache_dir: str | None = None,
|
hub_cache_dir: str | None = None,
|
||||||
trust_remote_code: bool = False,
|
trust_remote_code: bool = False,
|
||||||
|
**kwargs,
|
||||||
) -> dict[str, dict[int, gym.vector.VectorEnv]]:
|
) -> dict[str, dict[int, gym.vector.VectorEnv]]:
|
||||||
"""Makes a gym vector environment according to the config or Hub reference.
|
"""Makes a gym vector environment according to the config or Hub reference.
|
||||||
|
|
||||||
@@ -118,6 +119,9 @@ def make_env(
|
|||||||
hub_cache_dir (str | None): Optional cache path for downloaded hub files.
|
hub_cache_dir (str | None): Optional cache path for downloaded hub files.
|
||||||
trust_remote_code (bool): **Explicit consent** to execute remote code from the Hub.
|
trust_remote_code (bool): **Explicit consent** to execute remote code from the Hub.
|
||||||
Default False — must be set to True to import/exec hub `env.py`.
|
Default False — must be set to True to import/exec hub `env.py`.
|
||||||
|
**kwargs: Additional keyword arguments passed to the hub environment's `make_env` function.
|
||||||
|
Useful for passing custom configurations like `config_path`, `config_overrides`, etc.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: if n_envs < 1
|
ValueError: if n_envs < 1
|
||||||
ModuleNotFoundError: If the requested env package is not installed
|
ModuleNotFoundError: If the requested env package is not installed
|
||||||
@@ -149,9 +153,11 @@ def make_env(
|
|||||||
# import and surface clear import errors
|
# import and surface clear import errors
|
||||||
module = _import_hub_module(local_file, repo_id)
|
module = _import_hub_module(local_file, repo_id)
|
||||||
|
|
||||||
# call the hub-provided make_env
|
# call the hub-provided make_env with any additional kwargs
|
||||||
env_cfg = None if isinstance(cfg, str) else cfg
|
env_cfg = None if isinstance(cfg, str) else cfg
|
||||||
raw_result = _call_make_env(module, n_envs=n_envs, use_async_envs=use_async_envs, cfg=env_cfg)
|
raw_result = _call_make_env(
|
||||||
|
module, n_envs=n_envs, use_async_envs=use_async_envs, cfg=env_cfg, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
# normalize the return into {suite: {task_id: vec_env}}
|
# normalize the return into {suite: {task_id: vec_env}}
|
||||||
return _normalize_hub_result(raw_result)
|
return _normalize_hub_result(raw_result)
|
||||||
|
|||||||
@@ -311,20 +311,27 @@ def _import_hub_module(local_file: str, repo_id: str) -> Any:
|
|||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
||||||
def _call_make_env(module: Any, n_envs: int, use_async_envs: bool, cfg: EnvConfig | None) -> Any:
|
def _call_make_env(module: Any, n_envs: int, use_async_envs: bool, cfg: EnvConfig | None, **kwargs) -> Any:
|
||||||
"""
|
"""
|
||||||
Ensure module exposes make_env and call it.
|
Ensure module exposes make_env and call it with any additional kwargs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module: The imported hub module containing make_env.
|
||||||
|
n_envs: Number of parallel environments.
|
||||||
|
use_async_envs: Whether to use AsyncVectorEnv or SyncVectorEnv.
|
||||||
|
**kwargs: Additional keyword arguments to pass to the hub's make_env function.
|
||||||
|
Common examples include config_path, config_overrides, etc.
|
||||||
"""
|
"""
|
||||||
if not hasattr(module, "make_env"):
|
if not hasattr(module, "make_env"):
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
f"The hub module {getattr(module, '__name__', 'hub_module')} must expose `make_env(n_envs=int, use_async_envs=bool)`."
|
f"The hub module {getattr(module, '__name__', 'hub_module')} must expose `make_env(n_envs=int, use_async_envs=bool, **kwargs)`."
|
||||||
)
|
)
|
||||||
entry_fn = module.make_env
|
entry_fn = module.make_env
|
||||||
# Only pass cfg if it's not None (i.e., when an EnvConfig was provided, not a string hub ID)
|
# Only pass cfg if it's not None (i.e., when an EnvConfig was provided, not a string hub ID)
|
||||||
if cfg is not None:
|
if cfg is not None:
|
||||||
return entry_fn(n_envs=n_envs, use_async_envs=use_async_envs, cfg=cfg)
|
return entry_fn(n_envs=n_envs, use_async_envs=use_async_envs, cfg=cfg, **kwargs)
|
||||||
else:
|
else:
|
||||||
return entry_fn(n_envs=n_envs, use_async_envs=use_async_envs)
|
return entry_fn(n_envs=n_envs, use_async_envs=use_async_envs, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def _normalize_hub_result(result: Any) -> dict[str, dict[int, gym.vector.VectorEnv]]:
|
def _normalize_hub_result(result: Any) -> dict[str, dict[int, gym.vector.VectorEnv]]:
|
||||||
|
|||||||
@@ -43,6 +43,17 @@ lerobot-eval \
|
|||||||
|
|
||||||
Note that in both examples, the repo/folder should contain at least `config.json` and `model.safetensors` files.
|
Note that in both examples, the repo/folder should contain at least `config.json` and `model.safetensors` files.
|
||||||
|
|
||||||
|
You can also evaluate a model on a Hub environment with custom kwargs:
|
||||||
|
```
|
||||||
|
lerobot-eval \
|
||||||
|
--policy.path=HF_USER/HF_REPO \
|
||||||
|
--env=HF_USER/HF_REPO \
|
||||||
|
--eval.batch_size=1 \
|
||||||
|
--eval.n_episodes=10 \
|
||||||
|
--env_kwargs.environment=env_A \
|
||||||
|
--env_kwargs.embodiment=emb_B \
|
||||||
|
```
|
||||||
|
|
||||||
You can learn about the CLI options for this script in the `EvalPipelineConfig` in lerobot/configs/eval.py
|
You can learn about the CLI options for this script in the `EvalPipelineConfig` in lerobot/configs/eval.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -521,6 +532,7 @@ def eval_main(cfg: EvalPipelineConfig):
|
|||||||
n_envs=cfg.eval.batch_size,
|
n_envs=cfg.eval.batch_size,
|
||||||
use_async_envs=cfg.eval.use_async_envs,
|
use_async_envs=cfg.eval.use_async_envs,
|
||||||
trust_remote_code=cfg.trust_remote_code,
|
trust_remote_code=cfg.trust_remote_code,
|
||||||
|
**cfg.env_kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.info("Making policy.")
|
logging.info("Making policy.")
|
||||||
|
|||||||
@@ -266,3 +266,65 @@ def test_make_env_from_hub_async():
|
|||||||
|
|
||||||
# clean up
|
# clean up
|
||||||
env.close()
|
env.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_make_env_from_hub_with_kwargs():
|
||||||
|
"""Test that kwargs are correctly passed to hub environment's make_env."""
|
||||||
|
hub_id = "lerobot/dummy-hub-env"
|
||||||
|
|
||||||
|
# Test with config_path kwarg
|
||||||
|
envs_dict = make_env(
|
||||||
|
hub_id,
|
||||||
|
n_envs=1,
|
||||||
|
trust_remote_code=True,
|
||||||
|
config_path="/path/to/config.yaml",
|
||||||
|
)
|
||||||
|
env = envs_dict["cartpole_suite"][0]
|
||||||
|
|
||||||
|
assert hasattr(env, "hub_config")
|
||||||
|
assert env.hub_config["config_path"] == "/path/to/config.yaml"
|
||||||
|
env.close()
|
||||||
|
|
||||||
|
# Test with config_overrides dict
|
||||||
|
envs_dict = make_env(
|
||||||
|
hub_id,
|
||||||
|
n_envs=1,
|
||||||
|
trust_remote_code=True,
|
||||||
|
config_overrides={"scene.object": "microwave", "sim.dt": 0.01},
|
||||||
|
)
|
||||||
|
env = envs_dict["cartpole_suite"][0]
|
||||||
|
|
||||||
|
assert env.hub_config["config_overrides"]["scene.object"] == "microwave"
|
||||||
|
assert env.hub_config["config_overrides"]["sim.dt"] == 0.01
|
||||||
|
env.close()
|
||||||
|
|
||||||
|
# Test with arbitrary extra kwargs
|
||||||
|
envs_dict = make_env(
|
||||||
|
hub_id,
|
||||||
|
n_envs=1,
|
||||||
|
trust_remote_code=True,
|
||||||
|
custom_param="value",
|
||||||
|
another_param=42,
|
||||||
|
)
|
||||||
|
env = envs_dict["cartpole_suite"][0]
|
||||||
|
|
||||||
|
assert env.hub_config["extra_kwargs"]["custom_param"] == "value"
|
||||||
|
assert env.hub_config["extra_kwargs"]["another_param"] == 42
|
||||||
|
env.close()
|
||||||
|
|
||||||
|
# Test combining config_path, config_overrides, and extra kwargs
|
||||||
|
envs_dict = make_env(
|
||||||
|
hub_id,
|
||||||
|
n_envs=2,
|
||||||
|
trust_remote_code=True,
|
||||||
|
config_path="my_config.yaml",
|
||||||
|
config_overrides={"robot": "gr1"},
|
||||||
|
task_name="pick_and_place",
|
||||||
|
)
|
||||||
|
env = envs_dict["cartpole_suite"][0]
|
||||||
|
|
||||||
|
assert env.hub_config["config_path"] == "my_config.yaml"
|
||||||
|
assert env.hub_config["config_overrides"]["robot"] == "gr1"
|
||||||
|
assert env.hub_config["extra_kwargs"]["task_name"] == "pick_and_place"
|
||||||
|
assert env.num_envs == 2
|
||||||
|
env.close()
|
||||||
|
|||||||
Reference in New Issue
Block a user