diff --git a/docs/api/reference.md b/docs/api/reference.md index c6b15133..6938bd48 100644 --- a/docs/api/reference.md +++ b/docs/api/reference.md @@ -1,43 +1,102 @@ +--- +search: + exclude: true +--- + # API Reference -Auto-generated from the source docstrings via -[`mkdocstrings`](https://mkdocstrings.github.io/). The API surface below covers GeneLab's public -core. Extension packages document their own APIs in their own projects. +This page is generated from source docstrings via +[`mkdocstrings`](https://mkdocstrings.github.io/). It is shared by both language versions of the +site. Concept and how-to pages explain how to use the APIs; this page is for signatures, members, +and defaults. -!!! note "Single-source" - The API reference is generated from English docstrings and is shared by both language - versions of this site. +## Public facade ---- +::: genelab.lab + options: + show_if_no_docstring: true -## `genelab.lab` +## Registries and configs -The public facade. Most user code should import from here rather than from `genelab.registry` -directly. +::: genelab.registry + options: + show_if_no_docstring: true -::: genelab.lab +::: genelab.configs options: show_if_no_docstring: true ---- +## Environment -## `genelab.registry` +::: genelab.envs.manager_based_rl_env + options: + show_if_no_docstring: true -::: genelab.registry +## Managers + +::: genelab.managers options: show_if_no_docstring: true ---- +## MDP library -## `genelab.configs` +::: genelab.mdp + options: + show_if_no_docstring: true -::: genelab.configs +## Scene and entities + +::: genelab.scene options: show_if_no_docstring: true ---- +::: genelab.entity + options: + show_if_no_docstring: true + +## Actuators + +::: genelab.actuator + options: + show_if_no_docstring: true + +## Sensors + +::: genelab.sensor + options: + show_if_no_docstring: true + +## Terrains + +::: genelab.terrains + options: + show_if_no_docstring: true + +## RL + +::: genelab.rl + options: + show_if_no_docstring: true + +## Recording + +::: genelab.recording + options: + show_if_no_docstring: true + +## Bridges + +::: genelab.bridges + options: + show_if_no_docstring: true + +## Asset zoo + +::: genelab.asset_zoo + options: + show_if_no_docstring: true -## `genelab.cache` +## Utilities ::: genelab.cache options: diff --git a/docs/best-practices/debugging.en.md b/docs/best-practices/debugging.en.md new file mode 100644 index 00000000..aee40f01 --- /dev/null +++ b/docs/best-practices/debugging.en.md @@ -0,0 +1,88 @@ +# How to Debug Common Failures + +This guide lists the shortest checks for common GeneLab failures. + +## Unknown task + +```bash +uv run genelab list tasks +uv run genelab --import my_project.tasks list tasks +``` + +If the second command works, the package is importable only when you pass `--import`. Install an +entry point or keep using an explicit import. If neither works, confirm the package is installed or +its `src/` directory is on `PYTHONPATH`. + +## Unknown override path + +```bash +uv run genelab info TASK_ID +``` + +Copy the path from `Overridable cfg paths`. Remember that `play` shortcuts may retarget +`env.simulation.*` to `play_env.simulation.*` when a task has `play_env`. + +## Torch or Genesis import errors + +```bash +uv run python -c "import torch; print(torch.__version__, torch.version.cuda)" +uv run python -c "import genesis; print(genesis.__version__)" +``` + +Pick exactly one `torch-*` extra and resync if needed: + +```bash +uv sync --reinstall-package torch --extra torch-cu128 +``` + +## Cache or write-location errors + +```bash +uv run genelab cache +``` + +This creates project-local cache directories and points `XDG_CACHE_HOME` and `MPLCONFIGDIR` at +`.cache/`. + +## Viewer failures + +First run headless: + +```bash +uv run genelab play TASK_ID --steps 32 +``` + +Then try viewer mode: + +```bash +uv run genelab play TASK_ID --vis --steps 128 +``` + +Viewer failures are usually graphics-driver, display, or OpenGL platform problems rather than +registry problems. + +## Observation or reward shape errors + +Manager terms should return batch-shaped tensors: + +| Term type | Expected shape | +|---|---| +| observation term | `(num_envs, d)` or `(num_envs,)` | +| reward term | `(num_envs,)` | +| termination term | `(num_envs,)` bool | + +Avoid scalar `()` tensors. They can broadcast incorrectly or fail later in the runner. + +## Distributed env-count errors + +`--num_envs` is total across ranks and must divide evenly by `--gpus`. + +```bash +uv run genelab train TASK_ID --gpus 4 --num_envs 4096 +``` + +Use `--num_envs_per_gpu` to avoid division: + +```bash +uv run genelab train TASK_ID --gpus 4 --num_envs_per_gpu 1024 +``` diff --git a/docs/best-practices/debugging.zh.md b/docs/best-practices/debugging.zh.md new file mode 100644 index 00000000..fba5693a --- /dev/null +++ b/docs/best-practices/debugging.zh.md @@ -0,0 +1,85 @@ +# 如何调试常见问题 + +这份指南列出 GeneLab 常见失败的最短排查路径。 + +## Unknown task + +```bash +uv run genelab list tasks +uv run genelab --import my_project.tasks list tasks +``` + +如果第二条命令能看到任务,说明包只有在显式 `--import` 时才被导入。安装 entry point, +或继续使用显式导入。如果两条都不行,确认包已安装,或它的 `src/` 目录在 `PYTHONPATH` 上。 + +## Unknown override path + +```bash +uv run genelab info TASK_ID +``` + +从 `Overridable cfg paths` 复制路径。注意 task 有 `play_env` 时,`play` 的短标志可能把 +`env.simulation.*` 重定向到 `play_env.simulation.*`。 + +## Torch 或 Genesis 导入错误 + +```bash +uv run python -c "import torch; print(torch.__version__, torch.version.cuda)" +uv run python -c "import genesis; print(genesis.__version__)" +``` + +只选择一个 `torch-*` extra。需要时重新同步: + +```bash +uv sync --reinstall-package torch --extra torch-cu128 +``` + +## 缓存或写入目录错误 + +```bash +uv run genelab cache +``` + +这会创建项目本地缓存目录,并把 `XDG_CACHE_HOME` 和 `MPLCONFIGDIR` 指向 `.cache/`。 + +## Viewer 失败 + +先无界面运行: + +```bash +uv run genelab play TASK_ID --steps 32 +``` + +再尝试 viewer: + +```bash +uv run genelab play TASK_ID --vis --steps 128 +``` + +viewer 失败通常是图形驱动、display 或 OpenGL platform 问题,而不是注册表问题。 + +## Observation 或 reward shape 错误 + +Manager term 应返回带 batch 维度的 tensor: + +| Term 类型 | 期望 shape | +|---|---| +| observation term | `(num_envs, d)` 或 `(num_envs,)` | +| reward term | `(num_envs,)` | +| termination term | `(num_envs,)` bool | + +避免返回标量 `()` tensor。它可能错误广播,或在 runner 中延迟失败。 + +## 分布式 env 数错误 + +`--num_envs` 表示所有 rank 的总数,必须能被 `--gpus` 整除。 + +```bash +uv run genelab train TASK_ID --gpus 4 --num_envs 4096 +``` + +想避免除法语义时使用 `--num_envs_per_gpu`: + +```bash +uv run genelab train TASK_ID --gpus 4 --num_envs_per_gpu 1024 +``` diff --git a/docs/best-practices/extension-projects.en.md b/docs/best-practices/extension-projects.en.md new file mode 100644 index 00000000..f2eb2147 --- /dev/null +++ b/docs/best-practices/extension-projects.en.md @@ -0,0 +1,91 @@ +# How to Build an Extension Project + +Use an extension project when your robot, task, or experiment should live outside `src/genelab/`. +This is the normal shape for downstream research code. + +## 1. Generate a scaffold + +```bash +uv run genelab project new my_robot_project +``` + +The generated package has: + +```text +my_robot_project/ +├── pyproject.toml +└── src/my_robot_project/ + ├── config.py + ├── envs.py + ├── robots.py + └── tasks.py +``` + +## 2. Keep responsibilities separate + +| File | Responsibility | +|---|---| +| `config.py` | Dataclasses owned by the project. | +| `robots.py` | Robot asset/config factories. | +| `envs.py` | Runtime env classes or env factory helpers. | +| `tasks.py` | `register()` hook and task classes. | + +Avoid importing Genesis at module import time unless the object truly needs it. Registry discovery +should stay cheap. + +## 3. Register through one hook + +Expose a no-argument `register()` function: + +```python +def register() -> None: + register_robot(...) + register_env(...) + register_task(...) +``` + +Guard repeated registration when the hook may run more than once: + +```python +if "my-robot" not in ROBOTS: + register_robot(...) +``` + +## 4. Prefer entry points for daily use + +In `pyproject.toml`: + +```toml +[project.entry-points."genelab.extensions"] +my_robot_project = "my_robot_project.tasks:register" +``` + +Then install as editable: + +```bash +uv pip install -e my_robot_project +uv run genelab list tasks +``` + +Use explicit imports for temporary experiments: + +```bash +PYTHONPATH=my_robot_project/src \ + uv run genelab --import my_robot_project.tasks list tasks +``` + +## 5. Verify the package boundary + +Run these from outside the package directory: + +```bash +uv run python -c "import my_robot_project" +uv run genelab list tasks +uv run genelab info MyProject-Example-v0 +uv run genelab play MyProject-Example-v0 --steps 3 +``` + +## Expected result + +The project can be imported as a normal Python package, GeneLab discovers its entry point, and all +task commands work without editing GeneLab core. diff --git a/docs/best-practices/extension-projects.zh.md b/docs/best-practices/extension-projects.zh.md new file mode 100644 index 00000000..51d066d8 --- /dev/null +++ b/docs/best-practices/extension-projects.zh.md @@ -0,0 +1,88 @@ +# 如何构建扩展项目 + +当你的机器人、任务或实验不应该放进 `src/genelab/` 时,使用扩展项目。这是下游研究代码的常规形态。 + +## 1. 生成骨架 + +```bash +uv run genelab project new my_robot_project +``` + +生成的包结构: + +```text +my_robot_project/ +├── pyproject.toml +└── src/my_robot_project/ + ├── config.py + ├── envs.py + ├── robots.py + └── tasks.py +``` + +## 2. 分清文件职责 + +| 文件 | 职责 | +|---|---| +| `config.py` | 项目自己的 dataclass。 | +| `robots.py` | 机器人资产/配置 factory。 | +| `envs.py` | 运行时 env 类或 env factory helper。 | +| `tasks.py` | `register()` hook 和 task 类。 | + +除非对象确实需要 Genesis,否则不要在模块导入时启动 Genesis。注册表发现应保持轻量。 + +## 3. 通过单个 hook 注册 + +暴露无参 `register()` 函数: + +```python +def register() -> None: + register_robot(...) + register_env(...) + register_task(...) +``` + +hook 可能运行多次时,加幂等保护: + +```python +if "my-robot" not in ROBOTS: + register_robot(...) +``` + +## 4. 日常使用优先 entry point + +在 `pyproject.toml` 中: + +```toml +[project.entry-points."genelab.extensions"] +my_robot_project = "my_robot_project.tasks:register" +``` + +然后 editable 安装: + +```bash +uv pip install -e my_robot_project +uv run genelab list tasks +``` + +临时实验使用显式导入: + +```bash +PYTHONPATH=my_robot_project/src \ + uv run genelab --import my_robot_project.tasks list tasks +``` + +## 5. 验证包边界 + +从包目录外运行: + +```bash +uv run python -c "import my_robot_project" +uv run genelab list tasks +uv run genelab info MyProject-Example-v0 +uv run genelab play MyProject-Example-v0 --steps 3 +``` + +## 预期结果 + +项目能作为普通 Python 包导入,GeneLab 能发现 entry point,所有 task 命令都能运行,且不需要修改 GeneLab core。 diff --git a/docs/best-practices/rl-experiments.en.md b/docs/best-practices/rl-experiments.en.md new file mode 100644 index 00000000..bef217a6 --- /dev/null +++ b/docs/best-practices/rl-experiments.en.md @@ -0,0 +1,85 @@ +# How to Run RL Experiments + +This guide describes the practical flow for training and replaying GeneLab tasks with `genelab.rl`. + +## 1. Smoke-test before training + +Run these before any long job: + +```bash +uv run genelab info TASK_ID +uv run genelab play TASK_ID --agent zero --steps 32 +uv run genelab play TASK_ID --agent random --steps 64 +uv run genelab train TASK_ID --num_envs 64 --max_iterations 2 +``` + +This checks registry loading, env construction, action dimensions, observation groups, rewards, +terminations, and runner wiring. + +## 2. Pick the policy source for playback + +| Agent | Behavior | Use case | +|---|---|---| +| `zero` | Sends zero actions. | Passive physics and reset health checks. | +| `random` | Sends uniform random actions in `[-1, 1]`. | Action bounds and stability checks. | +| `trained` | Loads a checkpoint through the task runner. | Inspect trained behavior. | + +Checkpoint playback: + +```bash +uv run genelab play TASK_ID \ + --agent trained \ + --checkpoint logs/rsl_rl///model_300.pt +``` + +Passing `--checkpoint` defaults `--agent` to `trained`. + +## 3. Control env count deliberately + +Single process: + +```bash +uv run genelab train TASK_ID --num_envs 4096 +``` + +Distributed: + +```bash +uv run genelab train TASK_ID --gpus 4 --num_envs 4096 +``` + +With `--gpus 4`, `--num_envs 4096` means 4096 total, 1024 per rank. Use +`--num_envs_per_gpu` when you want to specify the per-rank value directly. + +## 4. Keep logs reproducible + +Use meaningful `experiment_name` and `run_name` in `RslRlOnPolicyRunnerCfg`. GeneLab writes: + +```text +logs/rsl_rl/// +├── params/env.json +├── params/agent.json +└── model_*.pt +``` + +Use `--log_dir PATH` only when you need an exact output directory, such as a distributed relaunch or +scripted comparison. + +## 5. Profile short runs first + +```bash +uv run genelab train TASK_ID \ + --prof \ + --prof-active 3 \ + --prof-repeat 1 \ + --max_iterations 10 +uv run genelab prof open logs/torch_profile +``` + +Profiler traces grow quickly. Start with a small active window and increase only after confirming +the trace size is manageable. + +## Expected result + +Every long run should have a passing smoke test, explicit env count semantics, inspectable +`params/*.json`, and a known checkpoint replay command. diff --git a/docs/best-practices/rl-experiments.zh.md b/docs/best-practices/rl-experiments.zh.md new file mode 100644 index 00000000..1e191005 --- /dev/null +++ b/docs/best-practices/rl-experiments.zh.md @@ -0,0 +1,81 @@ +# 如何运行 RL 实验 + +这份指南说明用 `genelab.rl` 训练和回放 GeneLab task 的实用流程。 + +## 1. 训练前先做 smoke test + +任何长任务前先跑: + +```bash +uv run genelab info TASK_ID +uv run genelab play TASK_ID --agent zero --steps 32 +uv run genelab play TASK_ID --agent random --steps 64 +uv run genelab train TASK_ID --num_envs 64 --max_iterations 2 +``` + +这会检查注册表加载、env 构造、action 维度、observation group、reward、termination 和 runner 接线。 + +## 2. 为回放选择策略来源 + +| Agent | 行为 | 用途 | +|---|---|---| +| `zero` | 发送零动作。 | 被动物理和 reset 健康检查。 | +| `random` | 发送 `[-1, 1]` 均匀随机动作。 | action 边界与稳定性检查。 | +| `trained` | 通过 task runner 加载 checkpoint。 | 检查训练后行为。 | + +checkpoint 回放: + +```bash +uv run genelab play TASK_ID \ + --agent trained \ + --checkpoint logs/rsl_rl///model_300.pt +``` + +传 `--checkpoint` 时,`--agent` 默认就是 `trained`。 + +## 3. 明确控制 env 数量 + +单进程: + +```bash +uv run genelab train TASK_ID --num_envs 4096 +``` + +分布式: + +```bash +uv run genelab train TASK_ID --gpus 4 --num_envs 4096 +``` + +`--gpus 4` 时,`--num_envs 4096` 表示总数 4096,每个 rank 1024。想直接指定每个 rank +数量时用 `--num_envs_per_gpu`。 + +## 4. 保持日志可复现 + +在 `RslRlOnPolicyRunnerCfg` 中使用有意义的 `experiment_name` 和 `run_name`。GeneLab 写入: + +```text +logs/rsl_rl/// +├── params/env.json +├── params/agent.json +└── model_*.pt +``` + +只有需要精确输出目录时才使用 `--log_dir PATH`,例如分布式重启或脚本化对比。 + +## 5. 先 profile 短 run + +```bash +uv run genelab train TASK_ID \ + --prof \ + --prof-active 3 \ + --prof-repeat 1 \ + --max_iterations 10 +uv run genelab prof open logs/torch_profile +``` + +profiler trace 增长很快。先用小 active 窗口确认体量,再扩大。 + +## 预期结果 + +每个长训练都应该有通过的 smoke test、明确的 env 数语义、可检查的 `params/*.json`,以及已知的 checkpoint 回放命令。 diff --git a/docs/best-practices/task-design.en.md b/docs/best-practices/task-design.en.md new file mode 100644 index 00000000..f652d385 --- /dev/null +++ b/docs/best-practices/task-design.en.md @@ -0,0 +1,94 @@ +# How to Design a GeneLab Task + +This guide shows how to structure a task that is easy to run, inspect, train, and extend. + +## 1. Start from the task id + +Pick a stable id before writing code: + +```text +---v0 +``` + +Examples: + +- `GeneLab-Inverted-Pendulum-v0` +- `Genelab-Velocity-Flat-Unitree-G1-v0` +- `MyProject-PickPlace-v0` + +Use the same id in `TaskCfg.name`, the `TASKS` registration, logs, and README commands. + +## 2. Keep `env` and `play_env` separate + +Use `env` for training defaults and `play_env` for human inspection. + +| Config | Typical defaults | +|---|---| +| `env` | Many envs, viewer off, curriculum and disturbances on, training sensors only. | +| `play_env` | One env, viewer on, mouse/bridge controls on, reduced randomization, optional plots. | + +Do not make users remember long overrides just to open a viewer. `genelab play TASK --vis` should +work with the task's play config. + +## 3. Put robot construction behind a factory + +Register a robot factory instead of a prebuilt object: + +```python +register_robot( + "my-robot", + get_my_robot_cfg, + description="My robot.", + cfg_type=MyRobotCfg, +) +``` + +Factories keep imports light and allow the CLI to list metadata without booting Genesis. + +## 4. Organize manager terms by intent + +Use descriptive names in every manager dict. These names become CLI override paths and log keys. + +```python +rewards_cfg = { + "track_lin_vel": RewardTermCfg(...), + "action_rate": RewardTermCfg(...), + "feet_slip": RewardTermCfg(...), +} +``` + +Avoid names like `r1`, `penalty2`, or `tmp`. A good name is stable enough to appear in a paper, +run config, or dashboard. + +## 5. Make observations explicit + +Use at least a `policy` group. Add a `critic` group when the runner needs privileged information. + +```python +observations_cfg = { + "policy": ObservationGroupCfg(terms=policy_terms, enable_corruption=True), + "critic": ObservationGroupCfg(terms=critic_terms, enable_corruption=False), +} +``` + +Keep corruption, scale, and clip settings in the term cfg so they are visible through +`genelab info TASK`. + +## 6. Validate with small rollouts + +Before long training, run: + +```bash +uv run genelab info TASK_ID +uv run genelab play TASK_ID --steps 32 +uv run genelab play TASK_ID --agent random --steps 64 +uv run genelab train TASK_ID --num_envs 64 --max_iterations 2 +``` + +This catches registration, scene construction, action dimension, observation shape, reward, and +runner wiring issues before a long job. + +## Expected result + +A well-designed task has a stable task id, a clear `TaskCfg`, a viewer-friendly `play_env`, readable +manager term names, documented overrides, and a short smoke command that exercises the full path. diff --git a/docs/best-practices/task-design.zh.md b/docs/best-practices/task-design.zh.md new file mode 100644 index 00000000..d089bdee --- /dev/null +++ b/docs/best-practices/task-design.zh.md @@ -0,0 +1,92 @@ +# 如何设计 GeneLab 任务 + +这份指南说明如何组织一个容易运行、检查、训练和扩展的 GeneLab task。 + +## 1. 先定 task id + +写代码前先选一个稳定 id: + +```text +---v0 +``` + +示例: + +- `GeneLab-Inverted-Pendulum-v0` +- `Genelab-Velocity-Flat-Unitree-G1-v0` +- `MyProject-PickPlace-v0` + +`TaskCfg.name`、`TASKS` 注册、日志和 README 命令里都使用同一个 id。 + +## 2. 分开 `env` 与 `play_env` + +`env` 放训练默认值,`play_env` 放人工检查默认值。 + +| 配置 | 典型默认值 | +|---|---| +| `env` | 多 env、viewer 关闭、课程和扰动开启、只保留训练传感器。 | +| `play_env` | 单 env、viewer 开启、鼠标/bridge 控制开启、随机化较少、可选实时曲线。 | + +不要让用户为了打开 viewer 记一串 override。`genelab play TASK --vis` 应该能直接用任务的 +play 配置运行。 + +## 3. 用 factory 构造机器人 + +注册 robot factory,而不是注册已经构造好的对象: + +```python +register_robot( + "my-robot", + get_my_robot_cfg, + description="My robot.", + cfg_type=MyRobotCfg, +) +``` + +factory 能保持导入轻量,也让 CLI 在不启动 Genesis 的情况下列出元信息。 + +## 4. 按意图命名 manager term + +每个 manager 字典都使用描述性名称。这些名称会成为 CLI override 路径和日志 key。 + +```python +rewards_cfg = { + "track_lin_vel": RewardTermCfg(...), + "action_rate": RewardTermCfg(...), + "feet_slip": RewardTermCfg(...), +} +``` + +避免 `r1`、`penalty2`、`tmp` 这类名字。好名字应该稳定到可以出现在论文、run config 或 +dashboard 中。 + +## 5. 显式组织 observation + +至少提供 `policy` 组。runner 需要特权信息时再加 `critic` 组。 + +```python +observations_cfg = { + "policy": ObservationGroupCfg(terms=policy_terms, enable_corruption=True), + "critic": ObservationGroupCfg(terms=critic_terms, enable_corruption=False), +} +``` + +corruption、scale、clip 设置放在 term cfg 中,这样 `genelab info TASK` 能直接展示。 + +## 6. 用小 rollout 验证 + +长训练前先跑: + +```bash +uv run genelab info TASK_ID +uv run genelab play TASK_ID --steps 32 +uv run genelab play TASK_ID --agent random --steps 64 +uv run genelab train TASK_ID --num_envs 64 --max_iterations 2 +``` + +这能在长任务前捕获注册、scene 构造、action 维度、observation shape、reward 和 runner 接线问题。 + +## 预期结果 + +一个设计良好的 task 应该有稳定 task id、清晰的 `TaskCfg`、适合 viewer 的 `play_env`、 +可读的 manager term 名、可查的 override,以及能跑通完整路径的短 smoke 命令。 diff --git a/docs/cli/list-info.en.md b/docs/cli/list-info.en.md index ba35bd19..477e4951 100644 --- a/docs/cli/list-info.en.md +++ b/docs/cli/list-info.en.md @@ -1,14 +1,9 @@ # Discovery: list and info -The `list` and `info` subcommands are the discovery surface — together they -turn an installed extension into a printable map of available tasks, envs, -robots, and the dotted-path keys that `play` / `train` can override. +Use `list` to see what extensions registered. Use `info` to inspect one registered object and copy +override paths. -## Listing the registry - -`list KIND` enumerates one of the three global registries (`robots`, `envs`, -`tasks`). The command imports every discovered extension first, so the listing -reflects the current install set. +## List registries ```bash uv run genelab list robots @@ -16,81 +11,37 @@ uv run genelab list envs uv run genelab list tasks ``` -Each row prints the registered name, description, and a per-entry detail line -(for tasks: `env=..., robot=..., trainable=...`; for envs / robots: the first -few scalar cfg fields). Tasks that registered `examples=[...]` strings via -`register_task(..., examples=[...])` show an `(N example(s))` suffix. - -## Inspecting a task - -`info NAME` resolves a single registered name and prints a detail panel. The -lookup falls through tasks → envs → robots in that order. On a TTY an unknown -name opens a `questionary` picker over the combined task / env / robot list; -in non-interactive contexts the command exits with the available-name -listing. +If an extension is not installed, import it explicitly: ```bash -uv run genelab info GeneLab-Sensors-Showcase-v0 +PYTHONPATH=examples/inverted_pendulum/src \ + uv run genelab --import genelab_inverted_pendulum.tasks list tasks ``` -The task panel contains three sections: - -1. **Description and meta** — the one-line description plus the `env=..., - robot=..., trainable=...` strip pulled from the task cfg. -2. **Overridable cfg paths** — a three-column table (`Path`, `Type`, - `Default`) generated by walking the cfg dataclass tree depth-first. Field - names beginning with `_` are skipped. Sub-dataclasses are expanded inline - under their parent's dotted path. -3. **Examples** — the verbatim `examples=[...]` lines the extension supplied at - registration time. They are display-only — `info` does not run them. +## Inspect a task -The same panel renders for envs and robots, but without the `env / robot / -trainable` meta strip. - -## Override paths +```bash +uv run genelab info GeneLab-Inverted-Pendulum-v0 +``` -Every row in the "Overridable cfg paths" table is the exact dotted path that -`play` and `train` accept on the command line. The CLI forwards each -`-- VALUE` flag into `genelab.configs.apply_overrides`, which coerces -the value against the target field's type hint at config build time. +The task view includes: -| `info` row | Equivalent override flag | +| Section | Meaning | |---|---| -| `env.simulation.vis` (bool, false) | `--vis` (shortcut) or `--env.simulation.vis true` | -| `env.simulation.gpu` (bool, false) | `--gpu` (shortcut) or `--env.simulation.gpu true` | -| `env.simulation.steps` (int, 240) | `--steps 500` (shortcut) or `--env.simulation.steps 500` | -| `env.simulation.dt` (float, 0.01) | `--dt 0.005` (shortcut) or `--env.simulation.dt 0.005` | -| `env.rewards_cfg.track_lin_vel.weight` (float, 1.0) | `--env.rewards_cfg.track_lin_vel.weight 2.0` | -| `env.rewards_cfg.track_lin_vel.params.std` (float, 0.25) | `--env.rewards_cfg.track_lin_vel.params.std 0.5` | -| `env.scene.env_spacing` (tuple, `(2.0, 2.0)`) | `--env.scene.env_spacing 3.0,3.0` | -| `env.scene.batch_render` (bool, false) | `--env.scene.batch_render true` | - -The four simulation shortcuts (`--vis`, `--gpu`, `--steps`, `--dt`) rewrite to -the `env.simulation.{vis,gpu,steps,dt}` paths before forwarding, so they show -up in profiler-trace argv exactly like an explicit override would. - -## Examples +| Metadata | Registered name, description, cfg type, examples. | +| Task config | `TaskCfg` summary: env, robot, trainable flag, agent. | +| Overridable paths | Dotted paths accepted by `play` and `train`. | -A short discovery loop for the inverted-pendulum task: +## Use copied override paths ```bash -# Confirm the extension is installed and pick a task id. -uv run genelab list tasks | grep Pendulum - -# Drill into one task — copy a dotted path from the "Overridable cfg paths" table. -uv run genelab info GeneLab-Inverted-Pendulum-v0 - -# Run the task with overrides applied. -uv run genelab play GeneLab-Inverted-Pendulum-v0 --vis --steps 240 \ - --env.rewards_cfg.pole_upright.weight 5.0 +uv run genelab play GeneLab-Inverted-Pendulum-v0 \ + --env.rewards_cfg.pole_upright.weight 4.0 ``` -The same flow works against envs and robots: `genelab info FrankaPandaCfg` (a -robot) reveals every `ArticulationCfg` field including the per-group -`actuators.*.stiffness` and `default_joint_pos.*` keys. +When `play_env` exists, play-mode shortcut flags target `play_env`; explicit paths remain explicit. ## See also -- [Play and Train](play-train.md) -- [Configs](../concepts/configs.md) -- [Managers and MDP terms](../concepts/managers.md) +- [Configuration Reference](../reference/configuration.md) +- [CLI Reference](../reference/cli.md) diff --git a/docs/cli/list-info.zh.md b/docs/cli/list-info.zh.md index 4601fa6c..2003eeac 100644 --- a/docs/cli/list-info.zh.md +++ b/docs/cli/list-info.zh.md @@ -1,13 +1,8 @@ # 发现:list 与 info -`list` 与 `info` 子命令是 GeneLab 的发现入口 —— 它们把已安装的扩展打印成一张 -"可用 task / env / robot + 可覆盖 dotted path" 的地图,供 `play` / `train` 的 -`-- VALUE` 旗标使用。 +用 `list` 查看扩展注册了什么。用 `info` 检查单个注册对象,并复制 override 路径。 -## 列举注册表 - -`list KIND` 枚举三个全局注册表之一(`robots` / `envs` / `tasks`)。命令会先导入所 -有已发现的扩展,因此列表反映当前安装集。 +## 列出注册表 ```bash uv run genelab list robots @@ -15,76 +10,37 @@ uv run genelab list envs uv run genelab list tasks ``` -每行打印注册名、说明,以及一条逐条 detail:task 显示 `env=..., robot=..., -trainable=...`;env / robot 显示首批几个标量 cfg 字段。通过 -`register_task(..., examples=[...])` 注册了 `examples=[...]` 字符串的 task 会附带 -`(N example(s))` 后缀。 - -## 查看任务 - -`info NAME` 解析单个注册名并打印 detail 面板。查找顺序是 task → env → robot。 -TTY 下名字找不到时会唤起 `questionary` 选择器,从 task / env / robot 名的并集 -中挑一个;非交互环境下沿用原行为,直接退出并列出可用名。 +如果扩展尚未安装,显式导入: ```bash -uv run genelab info GeneLab-Sensors-Showcase-v0 +PYTHONPATH=examples/inverted_pendulum/src \ + uv run genelab --import genelab_inverted_pendulum.tasks list tasks ``` -Task 面板分三块: - -1. **Description 与 meta** —— 一行 description,加 task cfg 上拉出的 - `env=..., robot=..., trainable=...` 元信息条。 -2. **Overridable cfg paths** —— 三列表格(`Path` / `Type` / `Default`), - 通过深度优先遍历 cfg dataclass 树生成。以 `_` 开头的字段名被跳过;嵌套 - dataclass 会以父节点 dotted path 为前缀就地展开。 -3. **Examples** —— 扩展在注册时提供的原文 `examples=[...]` 行。仅展示, - `info` 不会执行它们。 - -env 与 robot 渲染同样的面板,但没有 `env / robot / trainable` 元信息条。 +## 查看任务 -## Override 路径 +```bash +uv run genelab info GeneLab-Inverted-Pendulum-v0 +``` -"Overridable cfg paths" 表里的每一行就是 `play` / `train` 能直接接受的 dotted -path。CLI 把每个 `-- VALUE` 旗标转发给 -`genelab.configs.apply_overrides`,后者在 cfg 构建期按目标字段类型 hint 做 coerce。 +任务视图包括: -| `info` 输出行 | 等价的 override 旗标 | +| 区域 | 含义 | |---|---| -| `env.simulation.vis` (bool, false) | `--vis`(捷径)或 `--env.simulation.vis true` | -| `env.simulation.gpu` (bool, false) | `--gpu`(捷径)或 `--env.simulation.gpu true` | -| `env.simulation.steps` (int, 240) | `--steps 500`(捷径)或 `--env.simulation.steps 500` | -| `env.simulation.dt` (float, 0.01) | `--dt 0.005`(捷径)或 `--env.simulation.dt 0.005` | -| `env.rewards_cfg.track_lin_vel.weight` (float, 1.0) | `--env.rewards_cfg.track_lin_vel.weight 2.0` | -| `env.rewards_cfg.track_lin_vel.params.std` (float, 0.25) | `--env.rewards_cfg.track_lin_vel.params.std 0.5` | -| `env.scene.env_spacing` (tuple, `(2.0, 2.0)`) | `--env.scene.env_spacing 3.0,3.0` | -| `env.scene.batch_render` (bool, false) | `--env.scene.batch_render true` | - -四个 simulation 捷径(`--vis` / `--gpu` / `--steps` / `--dt`)在转发前会被改写成 -`env.simulation.{vis,gpu,steps,dt}` 形式,因此 profiler trace argv 中显示的形态 -与显式 override 完全一致。 - -## 示例命令清单 +| 元信息 | 注册名、描述、cfg 类型、示例命令。 | +| 任务配置 | `TaskCfg` 摘要:env、robot、trainable、agent。 | +| 可覆盖路径 | `play` 和 `train` 接受的 dotted path。 | -针对 inverted-pendulum 任务的发现流程: +## 使用复制出的 override 路径 ```bash -# 确认扩展已安装并挑一个 task id。 -uv run genelab list tasks | grep Pendulum - -# 钻入单 task —— 从 "Overridable cfg paths" 表里复制一个 dotted path。 -uv run genelab info GeneLab-Inverted-Pendulum-v0 - -# 带 override 跑 task。 -uv run genelab play GeneLab-Inverted-Pendulum-v0 --vis --steps 240 \ - --env.rewards_cfg.pole_upright.weight 5.0 +uv run genelab play GeneLab-Inverted-Pendulum-v0 \ + --env.rewards_cfg.pole_upright.weight 4.0 ``` -同样的流程对 env 与 robot 也成立:`genelab info FrankaPandaCfg`(robot)会列出 -所有 `ArticulationCfg` 字段,包括 per-group 的 `actuators.*.stiffness` 与 -`default_joint_pos.*` 键。 +当 task 有 `play_env` 时,play 模式短标志会作用到 `play_env`;显式路径仍保持显式语义。 -## See also +## 另见 -- [Play and Train](play-train.md) -- [Configs](../concepts/configs.md) -- [Managers and MDP terms](../concepts/managers.md) +- [配置参考](../reference/configuration.md) +- [CLI 参考](../reference/cli.md) diff --git a/docs/cli/overview.en.md b/docs/cli/overview.en.md index 284b8862..7db8dcff 100644 --- a/docs/cli/overview.en.md +++ b/docs/cli/overview.en.md @@ -1,75 +1,50 @@ -# CLI overview +# CLI Overview -`genelab` is exposed as a console script via the `genelab = "genelab.cli:main"` entry point. -With `uv` the canonical invocation is: +The `genelab` CLI is a thin dispatcher over registries and task configs. It does not own task logic; +it discovers extensions, resolves a registered object, applies overrides, and calls the task or +runner. + +## Command model ```bash -uv run genelab [GLOBAL OPTIONS] [ARGS] +uv run genelab [global options] [arguments] ``` -## Subcommands - -| Subcommand | Purpose | -|------------|---------| -| `cache` | Create project-local simulation cache directories (`.cache/`) and set `XDG_CACHE_HOME` / `MPLCONFIGDIR`. | -| `list robots` | List registered robots from the `ROBOTS` registry. | -| `list envs` | List registered environments from the `ENVS` registry. | -| `list tasks` | List registered tasks from the `TASKS` registry. | -| `play` | Run a registered task in the configured Genesis backend; supports config overrides. | -| `train` | Train a registered task when an RL runner exists; supports multi-GPU via `torchrun`. | -| `prof open` | Launch TensorBoard against a `torch.profiler` trace directory. | -| `project new` | Scaffold a new external extension package with all three registries wired up. | - -## Global options - -The following flags accept any position in front of the subcommand: +| Area | Commands | +|---|---| +| Registry discovery | `list robots`, `list envs`, `list tasks`, `info NAME` | +| Runtime | `play TASK`, `train TASK` | +| Utilities | `cache`, `prof open` | +| Project scaffolding | `project new NAME` | -| Flag | Effect | -|------|--------| -| `--version` | Print the GeneLab version and exit. | -| `--import MODULE` | Eagerly import an extension module before dispatching. Repeatable. Useful for extensions that do not (yet) ship a `genelab.extensions` entry point. | -| `--no-entry-points` | Skip auto-discovery via the `genelab.extensions` entry-point group. Combined with `--import`, produces a fully explicit, reproducible loading order. | +## Extension loading order -## Extension discovery order +Every command that needs registry data loads extensions in this order: -On startup the CLI discovers extensions through three pathways, in order: entry-point -auto-discovery, explicit `--import MODULE` flags, then programmatic -`genelab.registry.load_extension_module(...)` calls. +1. Bundled asset zoo robots through `load_bundled_asset_zoo()`. +2. Installed `genelab.extensions` entry points, unless `--no-entry-points` is set. +3. Repeated explicit `--import MODULE` values. -## Shell completion +Use entry points for daily work and `--import` for local experiments. -`--install-completion` writes a completion script into the current shell's rc file -(bash, zsh, fish, PowerShell); `--show-completion` prints it to stdout for manual -installation. After installation, tab-completing `genelab info ` cycles through -every registered task, env, and robot name; `genelab play ` and -`genelab train ` cycle through task names; `genelab list ` offers -`robots / envs / tasks`. +## Overrides -!!! note "Entry-point extensions only" +Runtime commands accept unknown `--a.b.c VALUE` options after the task id. The CLI forwards them to +`apply_overrides`. - Completion callbacks load extensions through the `genelab.extensions` - entry-point group. Ad-hoc `--import MODULE` registrations do not appear - in the completion list — the shell strips global flags from the argv it - hands the callback, so the imported module list is invisible at that - point. - -## Interactive recovery +```bash +uv run genelab play TASK_ID --env.simulation.dt 0.005 +``` -When stdin is a TTY, four user-input mistakes fall back to a `questionary` -picker instead of exiting with a one-line error: +Use `genelab info TASK_ID` to list valid paths. -- `play` / `train` invoked with no task id, or with one that does not match - any registered task. -- `info NAME` with an unknown name. -- `--agent KIND` with a value other than `zero` / `random` / `trained`. -- `--` override paths that do not exist on the resolved task's cfg. +## Interactive mode -Outside a TTY — CI, pipes, scripts, pytest — the picker no-ops and the -original error surfaces unchanged, so non-interactive callers observe -identical behavior to a build without the recovery layer. +When stdin is a TTY, the CLI can prompt for a missing task id, unknown registry name, invalid +`--agent`, or unknown override path. In CI and scripts it raises the same errors directly. ## See also +- [CLI Reference](../reference/cli.md) +- [Discovery: list and info](list-info.md) - [Play and Train](play-train.md) -- [Project new](project-new.md) -- [Extensions](../concepts/extensions.md) diff --git a/docs/cli/overview.zh.md b/docs/cli/overview.zh.md index 2857241b..225ea095 100644 --- a/docs/cli/overview.zh.md +++ b/docs/cli/overview.zh.md @@ -1,68 +1,47 @@ # CLI 总览 -`genelab` 通过 `genelab = "genelab.cli:main"` entry point 暴露为命令行脚本。配合 `uv` 时 -通常这样调用: +`genelab` CLI 是注册表和任务配置上的薄调度层。它不拥有任务逻辑;它负责发现扩展、解析已注册对象、应用 override,然后调用 task 或 runner。 + +## 命令模型 ```bash -uv run genelab [全局选项] <子命令> [参数] +uv run genelab [全局选项] <命令> [参数] ``` -## 子命令 - -| 子命令 | 作用 | -|--------|------| -| `cache` | 创建项目本地仿真缓存目录(`.cache/`),并设置 `XDG_CACHE_HOME` / `MPLCONFIGDIR`。 | -| `list robots` | 列出 `ROBOTS` 注册表里已注册的机器人。 | -| `list envs` | 列出 `ENVS` 注册表里已注册的环境。 | -| `list tasks` | 列出 `TASKS` 注册表里已注册的任务。 | -| `play` | 在 Genesis 后端运行已注册任务,支持配置 override。 | -| `train` | 任务带 RL runner 时启动训练,通过 `torchrun` 支持多 GPU。 | -| `prof open` | 针对 `torch.profiler` 跟踪目录启动 TensorBoard。 | -| `project new` | 生成一个新的下游扩展包骨架,三种注册表全部接通。 | - -## 全局选项 - -下列标志放在任意子命令之前: +| 区域 | 命令 | +|---|---| +| 注册表发现 | `list robots`、`list envs`、`list tasks`、`info NAME` | +| 运行时 | `play TASK`、`train TASK` | +| 工具 | `cache`、`prof open` | +| 项目骨架 | `project new NAME` | -| 标志 | 作用 | -|------|------| -| `--version` | 打印 GeneLab 版本并退出。 | -| `--import MODULE` | 在派发子命令前显式导入扩展模块。可重复。适合扩展尚未提供 `genelab.extensions` entry point 时使用。 | -| `--no-entry-points` | 跳过通过 `genelab.extensions` entry-point 组的自动发现。与 `--import` 搭配可实现完全显式、可复现的扩展加载。 | +## 扩展加载顺序 -## 扩展发现顺序 +任何需要注册表数据的命令都会按顺序加载: -CLI 启动时按三条路径依次发现扩展:entry-point 自动发现、显式 `--import MODULE`、程序内的 -`genelab.registry.load_extension_module(...)`。 +1. 通过 `load_bundled_asset_zoo()` 加载随包 asset zoo robot。 +2. 已安装的 `genelab.extensions` entry point,除非设置 `--no-entry-points`。 +3. 重复传入的显式 `--import MODULE`。 -## Tab 补全 { #tab-completion } +日常工作用 entry point,本地实验用 `--import`。 -`--install-completion` 会把补全脚本写入当前 shell(bash / zsh / fish / PowerShell)的 -rc 文件;`--show-completion` 则把脚本打印到 stdout,便于手动安装。安装完成后,tab 补全 -`genelab info ` 会列出每一个已注册的 task / env / robot 名;`genelab play ` -与 `genelab train ` 列出已注册 task;`genelab list ` 列出 -`robots / envs / tasks`。 +## Overrides -!!! note "仅覆盖 entry-point 扩展" +运行时命令在 task id 后接受未知的 `--a.b.c VALUE` 选项。CLI 会把它们转发给 +`apply_overrides`。 - 补全回调通过 `genelab.extensions` entry-point 组加载扩展。临时 - `--import MODULE` 注册的扩展不会出现在补全列表里 —— shell 在调用补全回调时 - 已经把全局 `--import` 标志从 argv 里剥离,回调看不到这部分模块。 - -## 交互式回退 { #interactive-recovery } +```bash +uv run genelab play TASK_ID --env.simulation.dt 0.005 +``` -stdin 为 TTY 时,下列四种输入错误会回退到 `questionary` 选择器,而不是直接报错退出: +用 `genelab info TASK_ID` 查看有效路径。 -- `play` / `train` 没传 task id,或 task id 没注册。 -- `info NAME` 名字找不到。 -- `--agent KIND` 不是 `zero` / `random` / `trained`。 -- `--` override 路径在已解析任务的 cfg 上不存在。 +## 交互模式 -非 TTY 环境(CI、管道、脚本、pytest)下选择器自动 no-op,错误以原始形态抛出, -非交互调用者观察到的行为与未加回退层时完全一致。 +stdin 是 TTY 时,CLI 可以为缺失 task id、未知注册表名字、非法 `--agent`、未知 override 路径弹出选择器。CI 和脚本中会直接抛出相同错误。 -## See also +## 另见 +- [CLI 参考](../reference/cli.md) +- [发现:list 与 info](list-info.md) - [play 与 train](play-train.md) -- [新建项目](project-new.md) -- [扩展加载](../concepts/extensions.md) diff --git a/docs/cli/play-train.en.md b/docs/cli/play-train.en.md index e01e74c1..2e5d2b88 100644 --- a/docs/cli/play-train.en.md +++ b/docs/cli/play-train.en.md @@ -1,96 +1,58 @@ # Play and Train -`play` and `train` share a common dispatch path: resolve `` against the `TASKS` -registry, construct its `TaskCfg`, apply any command-line overrides, and hand the configured -task to either a single rollout (`play`) or the task's RL runner (`train`). +`play` runs a registered task. `train` runs a registered task through a supported runner when the +task provides an agent config. ## Play ```bash -uv run genelab play [SHORT FLAGS] [-- OVERRIDES] +uv run genelab play TASK_ID --steps 128 +uv run genelab play TASK_ID --vis --steps 500 +uv run genelab play TASK_ID --agent random --steps 128 ``` -Short flags may appear before or after ``; the CLI normalises the order internally. +Policy sources: -!!! tip "Interactive recovery" +| Agent | Behavior | +|---|---| +| `zero` | Zero actions. Default when no checkpoint is given. | +| `random` | Uniform random actions in `[-1, 1]`. | +| `trained` | Load a checkpoint and use the runner inference policy. | - Omitting `` on a TTY opens a `questionary` picker over the - registered tasks. Unknown task ids, malformed `--agent` values, and - `--` override paths that do not exist on the resolved cfg fall - back to the same picker. Non-TTY callers — CI, pipes, scripts — see the - original error messages unchanged. - -### Simulation shortcuts - -The following short flags rewrite to the corresponding `env.simulation.*` overrides: - -| Flag | Equivalent override | Effect | -|------|--------------------|--------| -| `--vis` | `env.simulation.vis=true` | Enable Genesis visualization. | -| `--gpu N` | `env.simulation.gpu=N` | Pin the rollout to a single GPU index. | -| `--steps N` | `env.simulation.steps=N` | Cap episode length to `N` steps. | - -### Runner flags - -When the task carries an RL agent config, `play` also accepts the runner-side flags: - -| Flag | Effect | -|------|--------| -| `--checkpoint PATH` | Load a trained policy and run inference rollouts. Forces `--agent` to default to `trained`. | -| `--num-envs N` | Override the registered env count (parallel environments). | -| `--agent {zero,random,trained}` | Pick the policy source. Defaults to `trained` when `--checkpoint` is set, otherwise `zero`. | - -### Override grammar - -After the short flags, any `-- VALUE` argument is parsed as a config override: +Checkpoint replay: ```bash -uv run genelab play \ - --env.simulation.dt 0.005 \ - --env.actions.scale 0.5 \ - --env.observations.include_velocity true +uv run genelab play TASK_ID \ + --checkpoint logs/rsl_rl///model_300.pt ``` -The dotted path walks the `TaskCfg` dataclass tree (typically rooted at `env.*`). Values are -coerced from string using the field's type hint — `int`, `float`, `bool`, `Path`, `list[...]`, -`tuple[...]` are all supported. - ## Train ```bash -uv run genelab train [SHORT FLAGS] [-- OVERRIDES] +uv run genelab train TASK_ID --num_envs 4096 --max_iterations 300 ``` -Train requires the registered task to expose an RL runner (commonly `rsl_rl_lib`). The -override syntax is identical to `play`. +For distributed training: -| Flag | Effect | -|------|--------| -| `--gpus N` | Dispatch via `torchrun --standalone --nproc_per_node=N` for distributed training. The task's runner must be `torchrun`-compatible. | -| `--checkpoint PATH` | Resume training from a checkpoint file. | -| `--num-envs N` | Override the registered env count (parallel environments). | -| `--max-iterations N` | Cap the number of PPO learning iterations. | -| `--seed N` | Override the RNG seed used by the runner. | -| `--log-dir PATH` | Override the log root; defaults to `logs///`. | -| `--agent {zero,random,trained}` | Forwarded to the runner; mostly useful for diagnostic runs. | - -When `--gpus N > 1`, the CLI masks `CUDA_VISIBLE_DEVICES` to the requested devices so each rank -sees a distinct GPU. +```bash +uv run genelab train TASK_ID --gpus 4 --num_envs 4096 +``` -## Examples +`--num_envs` is total across ranks and must divide evenly by `--gpus`. Use +`--num_envs_per_gpu` for per-rank semantics. -```bash -# Quick local rollout with visualization. -uv run genelab play wuji_hand --vis --steps 200 +## Config overrides -# Train on 4 GPUs with a custom action scale. -uv run genelab train wuji_hand --gpus 4 --env.actions.scale 0.3 +Any unknown option after the task id is treated as a dotted config override: -# Resume from a checkpoint. -uv run genelab train wuji_hand --checkpoint logs/wuji_hand/run_42/model_100.pt +```bash +uv run genelab play TASK_ID \ + --env.simulation.dt 0.005 \ + --env.rewards_cfg.action_rate.weight -0.01 ``` ## See also -- [Configs](../concepts/configs.md) -- [Registry](../concepts/registry.md) +- [Run RL Experiments](../best-practices/rl-experiments.md) +- [CLI Reference](../reference/cli.md) +- [RL runner](../concepts/rl-runner.md) diff --git a/docs/cli/play-train.zh.md b/docs/cli/play-train.zh.md index d0a82247..1da655ad 100644 --- a/docs/cli/play-train.zh.md +++ b/docs/cli/play-train.zh.md @@ -1,92 +1,57 @@ # play 与 train -`play` 与 `train` 共享同一条派发路径:在 `TASKS` 注册表查找 ``,构造其 `TaskCfg`, -应用命令行 override,然后交给单次 rollout(`play`)或任务自带的 RL runner(`train`)。 +`play` 运行已注册任务。`train` 在 task 提供 agent 配置时,通过支持的 runner 训练任务。 -## play +## Play ```bash -uv run genelab play [短标志] [-- 覆盖项] +uv run genelab play TASK_ID --steps 128 +uv run genelab play TASK_ID --vis --steps 500 +uv run genelab play TASK_ID --agent random --steps 128 ``` -短标志可放在 `` 前或后;CLI 内部会自动整理顺序。 +策略来源: -!!! tip "交互式回退" +| Agent | 行为 | +|---|---| +| `zero` | 零动作。未提供 checkpoint 时默认。 | +| `random` | `[-1, 1]` 均匀随机动作。 | +| `trained` | 加载 checkpoint 并使用 runner inference policy。 | - 在 TTY 下省略 `` 会唤起 `questionary` 选择器,从已注册 task 中 - 挑一个。task id 拼错、`--agent` 值不合法、`--` override 路径在 - 已解析 cfg 上不存在,都会回退到同一个选择器。非 TTY 调用者(CI、管道、 - 脚本)看到的报错与未启用回退层时一致。 +checkpoint 回放: -### 仿真短标志 - -下列短标志会被改写为对应的 `env.simulation.*` override: - -| 标志 | 等价 override | 作用 | -|------|--------------|------| -| `--vis` | `env.simulation.vis=true` | 开启 Genesis 可视化。 | -| `--gpu N` | `env.simulation.gpu=N` | 把 rollout 锁定到指定 GPU。 | -| `--steps N` | `env.simulation.steps=N` | 限制 episode 步数。 | - -### 运行时标志 - -任务携带 RL agent 配置时,`play` 还接受下列 runner 侧标志: - -| 标志 | 作用 | -|------|------| -| `--checkpoint PATH` | 加载已训练策略并做推理 rollout;会让 `--agent` 默认为 `trained`。 | -| `--num-envs N` | 覆盖任务注册时的 env 数量(并行环境数)。 | -| `--agent {zero,random,trained}` | 选择策略来源。设置 `--checkpoint` 时默认 `trained`,否则默认 `zero`。 | - -### Override 语法 +```bash +uv run genelab play TASK_ID \ + --checkpoint logs/rsl_rl///model_300.pt +``` -短标志之后的任意 `-- VALUE` 都会被当作配置 override: +## Train ```bash -uv run genelab play \ - --env.simulation.dt 0.005 \ - --env.actions.scale 0.5 \ - --env.observations.include_velocity true +uv run genelab train TASK_ID --num_envs 4096 --max_iterations 300 ``` -点路径沿 `TaskCfg` 的 dataclass 树(通常以 `env.*` 为根)下钻。字符串值按目标字段类型注解 -自动转换 —— 支持 `int`、`float`、`bool`、`Path`、`list[...]`、`tuple[...]`。 - -## train +分布式训练: ```bash -uv run genelab train [短标志] [-- 覆盖项] +uv run genelab train TASK_ID --gpus 4 --num_envs 4096 ``` -`train` 需要任务暴露一个 RL runner(通常是 `rsl_rl_lib`)。Override 语法与 `play` 一致。 - -| 标志 | 作用 | -|------|------| -| `--gpus N` | 通过 `torchrun --standalone --nproc_per_node=N` 启动分布式训练。任务自身的 runner 必须支持 `torchrun`。 | -| `--checkpoint PATH` | 从 checkpoint 文件继续训练。 | -| `--num-envs N` | 覆盖任务注册时的 env 数量(并行环境数)。 | -| `--max-iterations N` | 限制 PPO 学习迭代次数。 | -| `--seed N` | 覆盖 runner 使用的随机种子。 | -| `--log-dir PATH` | 覆盖日志根目录,默认 `logs///`。 | -| `--agent {zero,random,trained}` | 转发给 runner;多用于诊断式运行。 | +`--num_envs` 表示所有 rank 的总数,必须能被 `--gpus` 整除。每 rank 语义用 +`--num_envs_per_gpu`。 -当 `--gpus N > 1` 时,CLI 还会按所选设备掩码 `CUDA_VISIBLE_DEVICES`,让每个 rank 看到独立 -GPU。 +## 配置 override -## 示例 +task id 后的任何未知选项都会被当作 dotted config override: ```bash -# 本地可视化跑一遍。 -uv run genelab play wuji_hand --vis --steps 200 - -# 4 GPU 训练,并修改 action scale。 -uv run genelab train wuji_hand --gpus 4 --env.actions.scale 0.3 - -# 从 checkpoint 继续。 -uv run genelab train wuji_hand --checkpoint logs/wuji_hand/run_42/model_100.pt +uv run genelab play TASK_ID \ + --env.simulation.dt 0.005 \ + --env.rewards_cfg.action_rate.weight -0.01 ``` -## See also +## 另见 -- [配置系统](../concepts/configs.md) -- [注册表](../concepts/registry.md) +- [运行 RL 实验](../best-practices/rl-experiments.md) +- [CLI 参考](../reference/cli.md) +- [RL runner](../concepts/rl-runner.md) diff --git a/docs/cli/profiling.en.md b/docs/cli/profiling.en.md index 975683c2..348e9c31 100644 --- a/docs/cli/profiling.en.md +++ b/docs/cli/profiling.en.md @@ -1,64 +1,47 @@ # Profiling -GeneLab ships a thin wrapper around `torch.profiler` for measuring per-step cost on both -training and inference. The profiler is opt-in: `play` and `train` accept `--prof` flags, -and the matching `GENELAB_PROFILE` environment variables serve as a fallback. Traces are -written in TensorBoard format. +GeneLab forwards profiler flags to `torch.profiler` through `genelab.rl.maybe_profile`. -## Enabling the profiler - -Both commands accept the same flags. A CLI flag wins over the env var when both are set. +## Enable profiling ```bash -uv run genelab train --prof --prof-out logs/myprof -GENELAB_PROFILE=1 GENELAB_PROFILE_OUT=logs/myprof uv run genelab train +uv run genelab train TASK_ID \ + --prof \ + --prof-active 3 \ + --prof-repeat 1 \ + --max_iterations 10 ``` -## Flags - -| Flag | Env-var equivalent | Effect | -|------|--------------------|--------| -| `--prof` | `GENELAB_PROFILE=1` | Turn the profiler on. | -| `--prof-out PATH` | `GENELAB_PROFILE_OUT` | Trace output directory (default `logs/torch_profile`). | -| `--prof-wait N` | `GENELAB_PROFILE_WAIT` | Steps skipped before each cycle (default `10`). | -| `--prof-warmup N` | `GENELAB_PROFILE_WARMUP` | Warmup steps inside the cycle (default `5`). | -| `--prof-active N` | `GENELAB_PROFILE_ACTIVE` | Steps actively recorded per cycle (default `10`). | -| `--prof-repeat N` | `GENELAB_PROFILE_REPEAT` | Cycle count (default `2`). | -| `--prof-record-shapes` | `GENELAB_PROFILE_RECORD_SHAPES=1` | Record tensor shapes for op-input attribution. | -| `--prof-with-stack` | `GENELAB_PROFILE_WITH_STACK=1` | Capture Python stack traces. High overhead. | - -## Schedule defaults - -A cycle covers `wait + warmup + active` steps, and `repeat` is the number of cycles. With -the defaults each cycle covers 25 steps and two cycles run before capture stops, so a -single `--prof` run records 20 active steps — enough to surface hot ops without producing -oversized traces. +Open traces: -For `train`, one step is one rollout call on `RslRlVecEnvWrapper.step`. For `play`, one -step is one policy/env iteration. To express the schedule in PPO iterations instead of -env steps, multiply `--prof-wait`, `--prof-warmup`, and `--prof-active` by -`num_steps_per_env`. +```bash +uv run genelab prof open logs/torch_profile +``` -## Viewing traces +## Flags -`genelab prof open` launches TensorBoard against a trace directory: +| Flag | Meaning | +|---|---| +| `--prof` | Enable profiling. | +| `--prof-out PATH` | Trace output directory. | +| `--prof-wait N` | Initial wait steps. | +| `--prof-warmup N` | Warmup steps before recording. | +| `--prof-active N` | Recorded steps per cycle. | +| `--prof-repeat N` | Number of cycles. | +| `--prof-record-shapes` | Record tensor shapes. | +| `--prof-with-stack` | Capture Python stacks. Higher overhead. | -```bash -uv run genelab prof open logs/torch_profile --port 6006 -``` +## Practical defaults -The command refuses to start when the directory does not exist, and prints an install -hint when `tensorboard` is not on `PATH`. +Start short. A profiler step is advanced once per env step in GeneLab's RL loop, so traces can grow +quickly on large vectorized runs. ## Distributed runs -!!! note "Rank-0 only" - Under torchrun, only rank 0 writes traces. Other ranks see `maybe_profile()` as a - no-op so workers do not overwrite each other's files. CLI flags propagate through the - torchrun relaunch unchanged. +Only the main process writes profiler traces. Keep `--prof-active` small when profiling distributed +training. ## See also -- [Play and Train](play-train.md) -- [Configs](../concepts/configs.md) -- [torch.profiler reference](https://pytorch.org/docs/stable/profiler.html) +- [Run RL Experiments](../best-practices/rl-experiments.md) +- [CLI Reference](../reference/cli.md) diff --git a/docs/cli/profiling.zh.md b/docs/cli/profiling.zh.md index 20f2f655..f03860de 100644 --- a/docs/cli/profiling.zh.md +++ b/docs/cli/profiling.zh.md @@ -1,59 +1,45 @@ # 性能剖析 -GeneLab 在 `torch.profiler` 之上提供了一层薄壳,用于测量训练与推理阶段每一步的开销。剖析 -默认关闭:`play` 与 `train` 通过 `--prof` 系列标志启用,对应的 `GENELAB_PROFILE` 环境变量 -作为后备。所有跟踪结果以 TensorBoard 格式写出。 +GeneLab 通过 `genelab.rl.maybe_profile` 把 profiler 标志转发给 `torch.profiler`。 -## 启用剖析器 - -两个命令接受同一组标志。当 CLI 标志与环境变量同时设置时,CLI 标志优先。 +## 启用 profiling ```bash -uv run genelab train --prof --prof-out logs/myprof -GENELAB_PROFILE=1 GENELAB_PROFILE_OUT=logs/myprof uv run genelab train +uv run genelab train TASK_ID \ + --prof \ + --prof-active 3 \ + --prof-repeat 1 \ + --max_iterations 10 ``` -## 参数 - -| 标志 | 等价环境变量 | 作用 | -|------|--------------|------| -| `--prof` | `GENELAB_PROFILE=1` | 启用剖析器。 | -| `--prof-out PATH` | `GENELAB_PROFILE_OUT` | 跟踪输出目录(默认 `logs/torch_profile`)。 | -| `--prof-wait N` | `GENELAB_PROFILE_WAIT` | 每个周期开始前跳过的步数(默认 `10`)。 | -| `--prof-warmup N` | `GENELAB_PROFILE_WARMUP` | 周期内的预热步数(默认 `5`)。 | -| `--prof-active N` | `GENELAB_PROFILE_ACTIVE` | 每周期实际记录的步数(默认 `10`)。 | -| `--prof-repeat N` | `GENELAB_PROFILE_REPEAT` | 周期数(默认 `2`)。 | -| `--prof-record-shapes` | `GENELAB_PROFILE_RECORD_SHAPES=1` | 记录张量形状,便于按 op 输入归因。 | -| `--prof-with-stack` | `GENELAB_PROFILE_WITH_STACK=1` | 抓取 Python 调用栈,开销较高。 | - -## 调度默认值 - -一个周期覆盖 `wait + warmup + active` 步,`repeat` 决定周期数。默认参数下每个周期覆盖 25 -步、共采集 2 个周期,单次 `--prof` 总计记录 20 个 active 步 —— 足以暴露热点 op,又不会 -生成过大的跟踪文件。 +打开 trace: -对 `train`,一步指 `RslRlVecEnvWrapper.step` 的一次 rollout 调用;对 `play`,一步指一次 -策略/环境迭代。若希望以 PPO 迭代而非环境步表达调度,将 `--prof-wait`、`--prof-warmup`、 -`--prof-active` 乘以 `num_steps_per_env` 即可。 +```bash +uv run genelab prof open logs/torch_profile +``` -## 查看跟踪结果 +## 参数 -`genelab prof open` 会针对跟踪目录启动 TensorBoard: +| 标志 | 含义 | +|---|---| +| `--prof` | 启用 profiling。 | +| `--prof-out PATH` | trace 输出目录。 | +| `--prof-wait N` | 初始等待步数。 | +| `--prof-warmup N` | 记录前 warmup 步数。 | +| `--prof-active N` | 每个周期记录的步数。 | +| `--prof-repeat N` | 周期数。 | +| `--prof-record-shapes` | 记录 tensor shape。 | +| `--prof-with-stack` | 捕获 Python stack,开销更高。 | -```bash -uv run genelab prof open logs/torch_profile --port 6006 -``` +## 实用默认值 -目录不存在时命令直接拒绝运行;若 `PATH` 中找不到 `tensorboard`,会给出安装提示。 +先跑短 profile。GeneLab 的 RL loop 每个 env step 推进一次 profiler step,大规模 vectorized run 的 trace 会增长很快。 ## 分布式运行 -!!! note "仅 rank 0 写出跟踪" - `torchrun` 下只有 rank 0 写出跟踪文件,其余 rank 上 `maybe_profile()` 退化为 no-op, - 避免多个 worker 互相覆盖。CLI 标志会原样穿过 torchrun 的重启流程。 +只有 main process 写 profiler trace。profile 分布式训练时保持 `--prof-active` 较小。 -## See also +## 另见 -- [play 与 train](play-train.md) -- [配置系统](../concepts/configs.md) -- [torch.profiler 参考](https://pytorch.org/docs/stable/profiler.html) +- [运行 RL 实验](../best-practices/rl-experiments.md) +- [CLI 参考](../reference/cli.md) diff --git a/docs/cli/project-new.en.md b/docs/cli/project-new.en.md index 63b43fee..25959001 100644 --- a/docs/cli/project-new.en.md +++ b/docs/cli/project-new.en.md @@ -1,7 +1,7 @@ -# Project new +# Project New -`genelab project new` scaffolds a downstream extension package — a self-contained Python -project that registers robots, environments, and tasks into GeneLab's global registries. +`genelab project new` creates a standalone extension package. Use it for downstream projects rather +than editing `src/genelab/`. ## Usage @@ -9,50 +9,38 @@ project that registers robots, environments, and tasks into GeneLab's global reg uv run genelab project new my_robot_project ``` -### Options +Options: -| Option | Default | Effect | -|--------|---------|--------| -| `--path PATH` | `./` | Output directory. Created if missing. | -| `--package NAME` | derived from project name | Python import name (e.g. `my_robot_project`). | -| `--task-id ID` | `/-v0` | The first task ID registered by the scaffold. | -| `--force` | off | Overwrite an existing target directory. Use with care. | +| Option | Description | +|---|---| +| `--path PATH`, `-p PATH` | Parent directory for the generated project. | +| `--package NAME` | Python package name. Defaults to normalized `NAME`. | +| `--task-id ID` | Initial task id. Defaults to `-Example-v0`. | +| `--force` | Overwrite scaffold files if the target exists. | -## Scaffold output +## Generated structure -``` +```text my_robot_project/ -├── pyproject.toml # with [project.entry-points."genelab.extensions"] ├── README.md -└── src/ - └── my_robot_project/ - ├── __init__.py # exposes register() entry-point callable - ├── config.py # task-specific dataclass plugged into TaskCfg.env - ├── robots.py # robot registrations - ├── envs.py # environment registrations - └── tasks.py # task registration (using --task-id) +├── pyproject.toml +└── src/my_robot_project/ + ├── __init__.py + ├── config.py + ├── envs.py + ├── robots.py + └── tasks.py ``` -The generated `pyproject.toml` declares: - -```toml -[project.entry-points."genelab.extensions"] -my_robot_project = "my_robot_project:register" -``` - -Once installed (`uv pip install -e ./my_robot_project`), the extension is discovered on the -next CLI invocation without any `--import` flag. - -## Post-scaffold workflow +## After scaffolding ```bash -cd my_robot_project -uv pip install -e . # install the extension into the GeneLab venv -uv run genelab list tasks # confirm the new task ID shows up -uv run genelab play --vis +uv pip install -e my_robot_project +uv run genelab list tasks +uv run genelab play MyRobotProject-Example-v0 --steps 3 ``` ## See also +- [Build an Extension Project](../best-practices/extension-projects.md) - [Extensions](../concepts/extensions.md) -- [Configs](../concepts/configs.md) diff --git a/docs/cli/project-new.zh.md b/docs/cli/project-new.zh.md index 4e0c3ec6..ad523964 100644 --- a/docs/cli/project-new.zh.md +++ b/docs/cli/project-new.zh.md @@ -1,7 +1,6 @@ # 新建项目 -`genelab project new` 生成一个下游扩展包骨架 —— 一个独立 Python 项目,把机器人、环境、 -任务注册进 GeneLab 的全局注册表。 +`genelab project new` 创建独立扩展包。下游项目应使用它,而不是直接修改 `src/genelab/`。 ## 用法 @@ -9,50 +8,38 @@ uv run genelab project new my_robot_project ``` -### 选项 +选项: -| 选项 | 默认 | 作用 | -|------|------|------| -| `--path PATH` | `./` | 输出目录,不存在时自动创建。 | -| `--package NAME` | 由项目名推导 | Python 导入名(如 `my_robot_project`)。 | -| `--task-id ID` | `/-v0` | 骨架注册的首个任务 ID。 | -| `--force` | 关闭 | 已存在目标目录时覆盖。慎用。 | +| 选项 | 说明 | +|---|---| +| `--path PATH`、`-p PATH` | 生成项目的父目录。 | +| `--package NAME` | Python 包名,默认由 `NAME` 规范化得到。 | +| `--task-id ID` | 初始 task id,默认 `-Example-v0`。 | +| `--force` | 目标已存在时覆盖骨架文件。 | -## 生成的目录结构 +## 生成结构 -``` +```text my_robot_project/ -├── pyproject.toml # 含 [project.entry-points."genelab.extensions"] ├── README.md -└── src/ - └── my_robot_project/ - ├── __init__.py # 暴露 register() entry-point 调用入口 - ├── config.py # 接入 TaskCfg.env 的任务专属 dataclass - ├── robots.py # 机器人注册 - ├── envs.py # 环境注册 - └── tasks.py # 任务注册(使用 --task-id) +├── pyproject.toml +└── src/my_robot_project/ + ├── __init__.py + ├── config.py + ├── envs.py + ├── robots.py + └── tasks.py ``` -生成的 `pyproject.toml` 声明: - -```toml -[project.entry-points."genelab.extensions"] -my_robot_project = "my_robot_project:register" -``` - -安装后(`uv pip install -e ./my_robot_project`),扩展在下次启动 CLI 时被自动发现,无需任何 -`--import` 标志。 - -## 后续步骤 +## 生成后 ```bash -cd my_robot_project -uv pip install -e . # 安装扩展到 GeneLab 的 venv -uv run genelab list tasks # 确认新任务 ID 出现 -uv run genelab play --vis +uv pip install -e my_robot_project +uv run genelab list tasks +uv run genelab play MyRobotProject-Example-v0 --steps 3 ``` -## See also +## 另见 +- [构建扩展项目](../best-practices/extension-projects.md) - [扩展加载](../concepts/extensions.md) -- [配置系统](../concepts/configs.md) diff --git a/docs/concepts/actuators.en.md b/docs/concepts/actuators.en.md index 58bd6700..b69275b8 100644 --- a/docs/concepts/actuators.en.md +++ b/docs/concepts/actuators.en.md @@ -1,92 +1,31 @@ # Actuators -`genelab.actuator` is the electromechanical layer between a manager-based RL policy and -Genesis. Each `ActuatorBase` owns a regex-matched slice of an articulation's actuated -joints and decides how the per-step joint position target reaches the simulator: either -through Genesis's implicit PD law, or through a Python-side torque computation pushed via -`control_dofs_force`. +Actuators are the layer between policy actions and Genesis joint control. They let a robot config +choose how each joint group is driven without changing the action term or task logic. -## Why a dedicated layer +## Why a dedicated actuator layer -Real robots are not monolithic — a single Unitree G1 carries six distinct motor classes -spread across knees, hips, shoulders, and ankles. Each class has different stiffness, -damping, effort, velocity, armature, and friction. Folding all of that into flat -`joint_kp` / `joint_kv` dicts on `ArticulationCfg` loses the per-motor identity and -prevents modelling velocity-dependent saturation. The actuator namespace restores the -group abstraction and adds three torque models. +Different robots need different control assumptions. A simple cart can use implicit PD targets; a +legged robot may need torque limits and DC motor saturation. GeneLab keeps those mechanics in +actuator configs attached to an `ArticulationCfg`. -## Three shipped models +## Built-in models -| Class | Channel | Torque computation | -|---|---|---| -| `ImplicitPDActuator` | `implicit_pd` | None — Genesis solves `tau = kp*(q* - q) - kv*q_dot` internally | -| `IdealPDActuator` | `force` | `tau = clip(kp*(q* - q) - kv*q_dot, ±effort_limit)` | -| `DCMotorActuator` | `force` | `IdealPD` plus linear de-rating in the driving direction | +| Model | Behavior | +|---|---| +| `ImplicitPDActuator` | Uses Genesis/simulator implicit PD control. | +| `IdealPDActuator` | Computes PD torque in Python and writes force targets. | +| `DCMotorActuator` | Extends ideal PD with motor limits and saturation behavior. | -`ImplicitPDActuator` is the numerical equivalent of the pre-M2 baseline — Genesis is -called with `set_dofs_kp` / `set_dofs_kv` and the per-step target reaches the engine -through `control_dofs_position`. `IdealPDActuator` zeroes the simulator-side PD and -becomes the canonical torque-control path for explicit effort limits. `DCMotorActuator` -adds a torque-speed curve: +Actuators match joint groups by configured names or expressions, then expose dimensions and control +logic to action terms. -`tau_max(q_dot) = saturation_effort * clip(1 - |q_dot| / velocity_limit, 0, 1)` +## Design guidance -The de-rating only applies to the driving direction (the sign of `tau_pd` matches the -sign of `q_dot`); reverse-braking torque retains the full `effort_limit`. This matches -the Isaac Lab `DCMotor` semantics — the back-EMF saturation does not penalise -regenerative braking. +Keep actuator grouping aligned with robot mechanics. Avoid one giant actuator if arm, hand, and base +joints need different gains, limits, or action scales. -## Composing onto an articulation +## Where to continue -`ArticulationCfg.actuators` is a `dict[str, ActuatorBaseCfg]`. Every actuated joint must -be covered by exactly one group; both unmatched and conflicting regex matches raise -`ValueError` at `Articulation.bind` time. Passive joints get explicit zero-gain -`ImplicitPDActuatorCfg` entries so the topology is visible in the config. - -```python -from genelab.actuator import DCMotorActuatorCfg, ImplicitPDActuatorCfg -from genelab.entity import ArticulationCfg - -cfg = ArticulationCfg( - mjcf_path="/path/to/robot.xml", - default_joint_pos={"cart_slide": 0.0, "pole_hinge": 0.0}, - actuators={ - "cart": ImplicitPDActuatorCfg( - target_names_expr=("cart_slide",), stiffness=80.0, damping=8.0, - action_scale=1.0, - ), - "pole": ImplicitPDActuatorCfg( - target_names_expr=("pole_hinge",), stiffness=0.0, damping=0.0, - ), - }, -) -``` - -`target_names_expr` is a tuple of regex patterns matched against the articulation's -joint names. Each group's `action_scale` is published through -`Articulation.action_scale_tensor`, which `JointPositionAction` uses by default when -`scale` is `None` on the action config. - -## Failure modes worth knowing - -* **Empty `actuators` dict** — the articulation rejects an articulation with no declared - actuators. Even a passive pendulum needs a zero-gain group covering its hinge. -* **Unmatched joint** — `ValueError` lists the joints that no regex covered. -* **Conflicting groups** — `ValueError` lists the joints that more than one group matched - along with the group names involved. -* **`DCMotorActuatorCfg.velocity_limit is None`** — the de-rating curve has no - breakpoint; `__post_init__` raises before the actuator gets a chance to run. - -## Switching G1 between models - -The bundled `examples/unitree` extension declares six `DCMotorActuatorCfg` groups (5020, -7520_14, 7520_22, 4010, waist, ankle). To run the same robot under the simulator's -implicit PD law instead, swap each `DCMotorActuatorCfg(...)` for an -`ImplicitPDActuatorCfg(...)` with the same `stiffness` / `damping` / `effort_limit`. The -articulation makes no global assumption about which channel is active — every group -chooses individually. - -## See also - -- [Configs](configs.md) -- [Sensors](sensors.md) +- [Task design](../best-practices/task-design.md) +- [API Reference](../api/reference.md) diff --git a/docs/concepts/actuators.zh.md b/docs/concepts/actuators.zh.md index 72d476f2..d5d553e1 100644 --- a/docs/concepts/actuators.zh.md +++ b/docs/concepts/actuators.zh.md @@ -1,62 +1,27 @@ # 执行器 -`genelab.actuator` 是 manager-based RL 策略与 Genesis 之间的机电层。每个 `ActuatorBase` 通过正则表达式占有一段 articulation 的可驱动关节,并决定单步关节位置目标如何抵达仿真器:要么走 Genesis 的隐式 PD 通道,要么在 Python 端算出力矩并通过 `control_dofs_force` 推下去。 +执行器是策略 action 与 Genesis joint control 之间的一层。它让机器人配置决定每组关节如何驱动, +而不需要修改 action term 或任务逻辑。 -## 为什么需要专门的一层 +## 为什么需要专门的执行器层 -真实机器人不是单一规格 —— 一台 Unitree G1 同时挂着 6 种不同电机,分布在膝、髋、肩、踝。各类电机的 stiffness、damping、effort、velocity、armature、friction 都不一样。把这些信息全压进 `ArticulationCfg` 上扁平的 `joint_kp` / `joint_kv` 字典会丢掉电机身份,也无法表达速度相关的力矩饱和。actuator 命名空间把分组抽象重新拿回来,并加上 3 种力矩模型。 +不同机器人需要不同控制假设。简单小车可以用隐式 PD 目标;腿式机器人可能需要力矩限制和 DC motor 饱和。GeneLab 把这些机制放在挂到 `ArticulationCfg` 的 actuator 配置里。 -## 自带 3 种模型 +## 内置模型 -| 类 | 通道 | 力矩计算 | -|---|---|---| -| `ImplicitPDActuator` | `implicit_pd` | 不算 —— Genesis 内部求解 `tau = kp*(q* - q) - kv*q_dot` | -| `IdealPDActuator` | `force` | `tau = clip(kp*(q* - q) - kv*q_dot, ±effort_limit)` | -| `DCMotorActuator` | `force` | `IdealPD` 加上仅驱动方向生效的线性退磁 | +| 模型 | 行为 | +|---|---| +| `ImplicitPDActuator` | 使用 Genesis/仿真器隐式 PD 控制。 | +| `IdealPDActuator` | 在 Python 中计算 PD torque,并写入 force target。 | +| `DCMotorActuator` | 在 ideal PD 基础上加入电机限制和饱和行为。 | -`ImplicitPDActuator` 与 M2 之前的实现数值等价 —— 对 Genesis 调 `set_dofs_kp` / `set_dofs_kv`,每步目标通过 `control_dofs_position` 进入仿真。`IdealPDActuator` 把仿真器内部 PD 增益置零,成为带显式 effort 上限的力矩控制规范路径。`DCMotorActuator` 在上面追加一条力矩-速度退磁曲线: +执行器按配置的关节名或表达式匹配关节组,并向 action term 暴露维度和控制逻辑。 -`tau_max(q_dot) = saturation_effort * clip(1 - |q_dot| / velocity_limit, 0, 1)` +## 设计建议 -退磁仅作用于驱动方向(`tau_pd` 与 `q_dot` 同号时);反向制动力矩保持完整的 `effort_limit`。这是 Isaac Lab `DCMotor` 的语义 —— 反电动势饱和不惩罚回馈制动。 +执行器分组应贴合机器人机构。若手臂、手、底座关节需要不同增益、限制或 action scale,不要放进一个巨大的 actuator。 -## 装配到 articulation 上 +## 继续阅读 -`ArticulationCfg.actuators` 是 `dict[str, ActuatorBaseCfg]`。每一个可驱动关节都必须被恰好一个组覆盖;未匹配和正则冲突都会在 `Articulation.bind` 时抛 `ValueError`。被动关节也要写一条零增益 `ImplicitPDActuatorCfg`,让拓扑结构在 config 中可见。 - -```python -from genelab.actuator import DCMotorActuatorCfg, ImplicitPDActuatorCfg -from genelab.entity import ArticulationCfg - -cfg = ArticulationCfg( - mjcf_path="/path/to/robot.xml", - default_joint_pos={"cart_slide": 0.0, "pole_hinge": 0.0}, - actuators={ - "cart": ImplicitPDActuatorCfg( - target_names_expr=("cart_slide",), stiffness=80.0, damping=8.0, - action_scale=1.0, - ), - "pole": ImplicitPDActuatorCfg( - target_names_expr=("pole_hinge",), stiffness=0.0, damping=0.0, - ), - }, -) -``` - -`target_names_expr` 是一组正则模式,对 articulation 的关节名做匹配。每组的 `action_scale` 通过 `Articulation.action_scale_tensor` 暴露 —— 当 `JointPositionAction.scale` 留 `None` 时(推荐默认)就会从这里取值。 - -## 需要知道的失败模式 - -* **`actuators` 字典为空** —— articulation 拒绝完全没有声明 actuator 的配置。哪怕是被动单摆,也要为它的 hinge 写一条零增益组。 -* **未匹配关节** —— `ValueError` 列出未被任何正则覆盖的关节。 -* **冲突分组** —— `ValueError` 列出被多个组同时命中的关节及涉及的组名。 -* **`DCMotorActuatorCfg.velocity_limit` 为 `None`** —— 退磁曲线无断点;`__post_init__` 在 actuator 实际运行前先抛错。 - -## 在 G1 上切换模型 - -仓库自带的 `examples/unitree` 扩展声明了 6 个 `DCMotorActuatorCfg` 组(5020、7520_14、7520_22、4010、waist、ankle)。如果想让同一台机器人走仿真器隐式 PD,把每条 `DCMotorActuatorCfg(...)` 替换为同样 `stiffness` / `damping` / `effort_limit` 的 `ImplicitPDActuatorCfg(...)` 即可。articulation 不对哪个通道被激活做全局假设 —— 每个组各自决定。 - -## See also - -- [Configs](configs.md) -- [Sensors](sensors.md) +- [设计任务](../best-practices/task-design.md) +- [API 参考](../api/reference.md) diff --git a/docs/concepts/asset_zoo.en.md b/docs/concepts/asset_zoo.en.md index 0c76852d..56405279 100644 --- a/docs/concepts/asset_zoo.en.md +++ b/docs/concepts/asset_zoo.en.md @@ -1,137 +1,30 @@ -# Asset zoo +# Asset Zoo -`genelab.asset_zoo` ships curated robot configurations as a bundled extension of the -core package (not part of the `genelab.lab` facade), so `from genelab.asset_zoo import -CartpoleCfg` works out of the box. Each entry pairs a -declarative `AssetSpec` (URL + md5 + filename, optionally + `archive_member`) with a -lazy factory that returns a fresh `ArticulationCfg`. The factories trigger an -md5-verified download only when invoked, so read-only commands like -`genelab list robots` never touch the network. +The asset zoo is a curated bundle of robot configs and downloadable assets shipped with GeneLab. +It is useful for examples and smoke tests, but downstream projects should still own their robot +configs in their own packages. -## Why a curated zoo +## Bundled assets -Isaac Lab ships 40+ ready-to-use robot configurations and MjLab ships three; both burn a -significant fraction of their distribution size on bundled MJCF / URDF files. GeneLab -keeps the runtime install lean by separating the **configuration code** (in-tree, in -this package) from the **asset blobs** (off-tree, in the -[`KraHsu/genelab-assets`](https://github.com/KraHsu/genelab-assets) repository). The -first call to a robot factory downloads its MJCF once into the project-local cache and -every subsequent call resolves to the cached path. +| Name | Factory | +|---|---| +| Unitree G1 | `UnitreeG1Cfg` | +| Unitree Go1 | `UnitreeGo1Cfg` | +| ANYmal C | `AnymalCCfg` | +| Franka Panda | `FrankaPandaCfg` | +| Cartpole | `CartpoleCfg` | +| G1 LAFAN1 motion | `g1_lafan1_dance1_subject2()` | -## Lifecycle of an asset +Importing `genelab.asset_zoo` registers bundled robots into `ROBOTS`. Asset downloads stay lazy and +only happen when a factory needs the actual file. -1. A robot module (e.g. `asset_zoo/cartpole.py`) declares a module-level constant - `AssetSpec(name=..., url=..., md5=..., filename=...)`. -2. Import of `genelab.asset_zoo` calls `register_robot(name, factory, ...)` as a side - effect. The factory is the function `CartpoleCfg`, not the eager result. -3. `genelab list robots` enumerates registered names without invoking factories. -4. `ROBOTS.get("cartpole")` invokes the factory, which calls `fetch_asset(spec)`. On a - cold cache the file lands in `/.cache/assets/cartpole//cartpole.xml` - after md5 verification. On warm cache the path resolves immediately. -5. The factory returns a fresh `ArticulationCfg` so downstream callers can mutate - `init_pos`, `default_joint_pos`, etc. without affecting siblings. +## Cache and verification -## Five built-in robots +Downloaded assets live under the project cache. Asset helpers can verify checksums before returning +paths, so tasks can depend on stable local files after the first fetch. -| Name | Cfg factory | DoF | Actuator groups | -|---|---|---|---| -| `cartpole` | `CartpoleCfg()` | 2 | `cart` (active PD), `pole` (passive zero-gain) | -| `franka` | `FrankaPandaCfg()` | 9 | `panda_arm` (high-PD, k=400), `panda_hand` (stiff grasp, k=1e4) | -| `g1` | `UnitreeG1Cfg()` | 29 | `5020` / `7520_14` / `7520_22` / `4010` / `waist` / `ankle` — six DCMotor families | -| `go1` | `UnitreeGo1Cfg()` | 12 | `hip` / `thigh` / `calf` (ImplicitPD, k=25) | -| `anymal-c` | `AnymalCCfg()` | 12 | `legs` (single ImplicitPD group, k=80) | +## Where to continue -Use `genelab info ` to print the full override-path tree once registered. -Cartpole mirrors `examples/inverted_pendulum`'s gains; G1 mirrors -`examples/unitree/.../g1/constants.py`; Franka, Go1, and Anymal C follow Isaac Lab's -published defaults so cross-stack experiments stay comparable. The four Menagerie-sourced -entries (`franka`, `g1`, `go1`, `anymal-c`) ship as `.tar.gz` archives so meshes and -textures travel with the MJCF. - -## Cache layout and md5 verification - -Single-file mode (cartpole): - -``` -.cache/assets/// -``` - -Archive mode (franka, g1, go1, anymal-c): - -``` -.cache/assets///extracted/ -``` - -`fetch_asset` writes the download to a `..part` sibling and renames atomically -once the digest matches. Archive blobs go through a second atomic step: the tar is -expanded into a `.extracting/` sibling using `tarfile`'s `data` filter (rejects symlinks -and parent-directory escapes) and renamed to `extracted/` only on success. A mismatch -raises `AssetDownloadError` carrying both expected and actual digests; cached files -keyed by a stale md5 are auto-pruned at the next call with the corrected hash, since -the path itself moves. - -!!! warning "Update the md5 every time the asset moves" - The cache key is `(name, md5)`; bumping the md5 invalidates old copies cleanly. - Forgetting to bump it after re-uploading a fixed MJCF can leave clients reading a - stale local file forever. - -## Adding a new robot config - -Single-file MJCF (no external mesh dependencies): - -```python -_MJCF = AssetSpec( - name="my-robot", - url="https://raw.githubusercontent.com/KraHsu/genelab-assets/main/my-robot/my-robot.xml", - md5="<32 hex chars>", - filename="my-robot.xml", -) -``` - -Menagerie-style folder with meshes / textures (pack as `.tar.gz`, name the entry MJCF): - -```python -_MJCF = AssetSpec( - name="my-robot", - url="https://raw.githubusercontent.com/KraHsu/genelab-assets/main/my_robot/my_robot.tar.gz", - md5="<32 hex chars>", - filename="my_robot.tar.gz", - archive_member="my_robot/my_robot.xml", -) -``` - -Either way the factory is the same: - -```python -def MyRobotCfg() -> ArticulationCfg: - return ArticulationCfg( - mjcf_path=str(fetch_asset(_MJCF)), - actuators={"all": ImplicitPDActuatorCfg(target_names_expr=(r".*",), stiffness=100.0, damping=5.0)}, - ) - -register_robot("my-robot", MyRobotCfg, description="...", cfg_type=ArticulationCfg) -``` - -Then add the module to `asset_zoo/__init__.py` so import side-effects run during -`load_bundled_asset_zoo()`. Downstream projects that prefer to keep robots out-of-tree -should use the same `AssetSpec` + `register_robot` pattern from their own extension -package — the helpers are public. - -## Failure modes worth knowing - -* **Network unavailable** — `AssetDownloadError` wraps `URLError`; the staging file is - removed so a retry starts clean. -* **md5 mismatch** — raises `AssetDownloadError` with both digests; the staging file is - removed before the exception propagates. -* **Stale cache after upstream re-upload** — the path is keyed by md5, so updating the - spec naturally points at a fresh subdirectory; old digests linger on disk until the - user deletes `.cache/assets//` manually. -* **Factory called before `load_bundled_asset_zoo()`** — `ROBOTS.get("cartpole")` - raises `KeyError`; the CLI calls the loader at startup so this only bites direct API - use. Import `genelab.asset_zoo` explicitly to register without going through the CLI. - -## See also - -- [Registry](registry.md) -- [Actuators](actuators.md) -- [Extensions](extensions.md) +- [Module Map](../reference/module-map.md) +- [Unitree G1](../examples/unitree-g1.md) +- [API Reference](../api/reference.md) diff --git a/docs/concepts/asset_zoo.zh.md b/docs/concepts/asset_zoo.zh.md index 235accd4..a71e5a9e 100644 --- a/docs/concepts/asset_zoo.zh.md +++ b/docs/concepts/asset_zoo.zh.md @@ -1,98 +1,26 @@ # 资产库 -`genelab.asset_zoo` 把精选的机器人配置作为核心包的预置扩展一起发布(不属于 `genelab.lab` 公开门面),所以 `from genelab.asset_zoo import CartpoleCfg` 开箱即用。每个条目把一个声明式的 `AssetSpec`(URL + md5 + 文件名,可选 `archive_member`)和一个返回新 `ArticulationCfg` 的 lazy factory 配对。factory 仅在被调用时才触发带 md5 校验的下载,所以像 `genelab list robots` 这种只读命令不会触网。 +asset zoo 是 GeneLab 随包提供的一组机器人配置和可下载资产。它适合示例和 smoke test,但下游项目仍应在自己的包里维护机器人配置。 -## 为什么需要预置库 +## 随包资产 -Isaac Lab 自带 40+ 现成机器人配置,MjLab 自带 3 个;两者都把可观比例的发行体积花在内置 MJCF / URDF 文件上。GeneLab 把**配置代码**(在树内、本包内)和**资产二进制**(在树外,放在 [`KraHsu/genelab-assets`](https://github.com/KraHsu/genelab-assets) 仓库)分开,让运行时安装保持精简。第一次调用机器人 factory 会把 MJCF 下载一次到项目本地缓存,后续每次调用都解析到缓存路径。 +| 名称 | Factory | +|---|---| +| Unitree G1 | `UnitreeG1Cfg` | +| Unitree Go1 | `UnitreeGo1Cfg` | +| ANYmal C | `AnymalCCfg` | +| Franka Panda | `FrankaPandaCfg` | +| Cartpole | `CartpoleCfg` | +| G1 LAFAN1 motion | `g1_lafan1_dance1_subject2()` | -## 资产生命周期 +导入 `genelab.asset_zoo` 会把随包机器人注册进 `ROBOTS`。资产下载保持惰性,只有 factory 真正需要文件时才发生。 -1. 机器人模块(例如 `asset_zoo/cartpole.py`)在模块级别声明一个常量 `AssetSpec(name=..., url=..., md5=..., filename=...)`。 -2. 导入 `genelab.asset_zoo` 时副作用调用 `register_robot(name, factory, ...)`。factory 是函数 `CartpoleCfg`,不是立即求值的结果。 -3. `genelab list robots` 枚举已注册名称而不调用 factory。 -4. `ROBOTS.get("cartpole")` 调用 factory,factory 内调 `fetch_asset(spec)`。冷缓存时文件落到 `/.cache/assets/cartpole//cartpole.xml`,md5 校验通过后才生效。热缓存时路径立刻解析。 -5. factory 返回一个新的 `ArticulationCfg`,下游可以随意改 `init_pos`、`default_joint_pos` 等字段而不影响兄弟实例。 +## 缓存与校验 -## 自带 5 种机器人 +下载资产位于项目缓存目录。资产 helper 可以在返回路径前校验 checksum,因此首次获取后,任务能依赖稳定的本地文件。 -| 名称 | Cfg factory | DoF | actuator 分组 | -|---|---|---|---| -| `cartpole` | `CartpoleCfg()` | 2 | `cart`(带 PD)、`pole`(被动零增益) | -| `franka` | `FrankaPandaCfg()` | 9 | `panda_arm`(高 PD,k=400)、`panda_hand`(强抓握,k=1e4) | -| `g1` | `UnitreeG1Cfg()` | 29 | `5020` / `7520_14` / `7520_22` / `4010` / `waist` / `ankle` —— 6 个 DCMotor 家族 | -| `go1` | `UnitreeGo1Cfg()` | 12 | `hip` / `thigh` / `calf`(ImplicitPD,k=25) | -| `anymal-c` | `AnymalCCfg()` | 12 | `legs`(单 ImplicitPD 组,k=80) | +## 继续阅读 -注册完成后用 `genelab info ` 打印完整的 override 路径树。Cartpole 的增益与 `examples/inverted_pendulum` 保持一致;G1 与 `examples/unitree/.../g1/constants.py` 对齐;Franka、Go1 与 Anymal C 沿用 Isaac Lab 公开的默认值,方便跨栈对比实验。4 个 Menagerie 来源条目(`franka`、`g1`、`go1`、`anymal-c`)以 `.tar.gz` archive 形式发布,mesh 与纹理随 MJCF 一并发送。 - -## 缓存目录与 md5 校验 - -单文件模式(cartpole): - -``` -.cache/assets/// -``` - -archive 模式(franka、g1、go1、anymal-c): - -``` -.cache/assets///extracted/ -``` - -`fetch_asset` 先把下载内容写到 `..part` 兄弟文件,digest 校验通过后再原子 rename。archive 模式多一步原子语义:tar 先解到 `.extracting/` 临时目录(使用 `tarfile` 的 `data` filter,拒绝软链接与父目录逃逸),成功后再 rename 为 `extracted/`。不匹配会抛 `AssetDownloadError`,错误信息携带期望与实际两份 digest;旧 md5 对应的缓存目录会在下次用新 hash 调用时被自然绕过,因为路径本身改变了。 - -!!! warning "资产每次替换都要更新 md5" - 缓存 key 是 `(name, md5)`;更新 md5 可以干净失效旧副本。如果重新上传了修复版 MJCF 但忘了改 md5,客户端会一直读到陈旧的本地文件。 - -## 增加新的 robot 配置 - -单文件 MJCF(无外部 mesh 依赖): - -```python -_MJCF = AssetSpec( - name="my-robot", - url="https://raw.githubusercontent.com/KraHsu/genelab-assets/main/my-robot/my-robot.xml", - md5="<32 hex chars>", - filename="my-robot.xml", -) -``` - -Menagerie 风格带 mesh / texture 的目录(打成 `.tar.gz`,声明入口 MJCF): - -```python -_MJCF = AssetSpec( - name="my-robot", - url="https://raw.githubusercontent.com/KraHsu/genelab-assets/main/my_robot/my_robot.tar.gz", - md5="<32 hex chars>", - filename="my_robot.tar.gz", - archive_member="my_robot/my_robot.xml", -) -``` - -两种模式共用同一个 factory: - -```python -def MyRobotCfg() -> ArticulationCfg: - return ArticulationCfg( - mjcf_path=str(fetch_asset(_MJCF)), - actuators={"all": ImplicitPDActuatorCfg(target_names_expr=(r".*",), stiffness=100.0, damping=5.0)}, - ) - -register_robot("my-robot", MyRobotCfg, description="...", cfg_type=ArticulationCfg) -``` - -随后把模块加进 `asset_zoo/__init__.py`,让 `load_bundled_asset_zoo()` 触发导入副作用。下游项目若想把机器人放在树外,可以在自己的扩展包里用同样的 `AssetSpec` + `register_robot` 模式 —— 这两个 helper 都是公开 API。 - -## 需要知道的失败模式 - -* **网络不可达** —— `AssetDownloadError` 包住 `URLError`;staging 文件已删除,重试从干净状态开始。 -* **md5 不匹配** —— 抛 `AssetDownloadError`,错误带两份 digest;staging 文件在异常抛出前已删除。 -* **上游重新上传后缓存陈旧** —— 路径按 md5 键控,更新 spec 自然指到新子目录;旧 digest 对应的目录留在磁盘上直到用户手动删除 `.cache/assets//`。 -* **factory 在 `load_bundled_asset_zoo()` 之前被调** —— `ROBOTS.get("cartpole")` 抛 `KeyError`;CLI 启动时会调用 loader,所以这只有在直接用 API 时才会出现。需要绕过 CLI 时显式 `import genelab.asset_zoo` 即可注册。 - -## See also - -- [Registry](registry.md) -- [Actuators](actuators.md) -- [Extensions](extensions.md) +- [模块地图](../reference/module-map.md) +- [Unitree G1](../examples/unitree-g1.md) +- [API 参考](../api/reference.md) diff --git a/docs/concepts/configs.en.md b/docs/concepts/configs.en.md index 07805366..aeb59455 100644 --- a/docs/concepts/configs.en.md +++ b/docs/concepts/configs.en.md @@ -1,65 +1,42 @@ # Configs -The config system is a small dataclass hierarchy under `genelab.configs`: +GeneLab configuration is a dataclass tree. The tree is intentionally ordinary Python rather than a +custom schema language: task authors can construct it with normal code, and the CLI can still expose +stable override paths. -``` -TaskCfg -└── env: object # downstream extensions plug in their own env dataclass - ManagerBasedEnvCfg - ├── simulation - ├── scene - ├── actions - ├── observations - ├── rewards - ├── terminations - └── events -``` - -`TaskCfg.env` is typed `object` on purpose — downstream extensions plug their own env dataclass -into it without touching core. Most users subclass `ManagerBasedEnvCfg` for their `env` field. +## The core idea -## apply_overrides +`TaskCfg` is the boundary between discovery and runtime. It records the task id, the registered env +and robot names, the default env config, an optional play-mode env config, and an optional agent +config. -The headline function is `apply_overrides(cfg, dict)`, which parses dotted paths and applies -them to the config tree: - -```python -from genelab.configs import apply_overrides - -apply_overrides(cfg, { - "env.simulation.dt": "0.005", - "env.simulation.steps": "500", - "env.robot.actuators.cart.stiffness": "100.0", - "env.observations.include_velocity": "true", -}) +```text +TaskCfg +├── env +├── play_env +└── agent ``` -### Type coercion +`env` is typed as `object` on purpose. Downstream projects can use a subclass such as +`ManagerBasedRlEnvCfg` without changing GeneLab core. -String values are coerced using the field's type hint on the target dataclass. Supported -targets: +## Why dotted overrides work -| Hint | Accepted strings | -|------|------------------| -| `bool` | `true`/`false`/`1`/`0`/`yes`/`no` (case-insensitive) | -| `int` | any base-10 integer literal | -| `float` | any Python float literal | -| `Path` | passed through `pathlib.Path(...)` | -| `list[T]` | comma-separated, each element coerced as `T` | -| `tuple[T, ...]` | comma-separated, each element coerced as `T` | -| `str` | identity | +The CLI treats `--a.b.c VALUE` as a path through this dataclass tree. `apply_overrides` resolves the +path, reads the current value and annotation, coerces the string value, and mutates the config before +runtime starts. -When a path does not resolve to a known field or a value cannot be coerced, `apply_overrides` -raises a descriptive error at config build time, not at simulation runtime. +That keeps experiments reproducible: the final `TaskCfg` can be dumped, and invalid paths fail early +instead of silently being ignored. -### Forwarding from the CLI +## Play configs are first-class -The `play` / `train` subcommands forward every `-- VALUE` flag into `apply_overrides`. -The three simulation shortcuts (`--vis`, `--gpu`, `--steps`) rewrite to -`env.simulation.{vis,gpu,steps}` overrides before forwarding. +Training and inspection often need different defaults. `play_env` lets a task keep viewer-friendly +settings next to training settings: fewer envs, viewer on, optional mouse interaction, and optional +recording outputs. -## See also +## Where to continue -- [Actuators](actuators.md) -- [Play and Train](../cli/play-train.md) -- [API Reference](../api/reference.md) +- [Configuration Reference](../reference/configuration.md) +- [Design a Task](../best-practices/task-design.md) +- [CLI Reference](../reference/cli.md) diff --git a/docs/concepts/configs.zh.md b/docs/concepts/configs.zh.md index 73c438a7..d19a44ba 100644 --- a/docs/concepts/configs.zh.md +++ b/docs/concepts/configs.zh.md @@ -1,62 +1,34 @@ # 配置系统 -GeneLab 的配置体系是 `genelab.configs` 下的一个小型 dataclass 层级: +GeneLab 配置是一棵 dataclass 树。它刻意使用普通 Python,而不是自定义 schema:任务作者可以用常规代码构造配置,CLI 也仍然能暴露稳定的 override 路径。 -``` -TaskCfg -└── env: object # 下游扩展把自己的 env dataclass 接入这里 - ManagerBasedEnvCfg - ├── simulation - ├── scene - ├── actions - ├── observations - ├── rewards - ├── terminations - └── events -``` - -`TaskCfg.env` 类型刻意写成 `object` —— 下游扩展无需触碰核心即可接入自己的 env dataclass。 -大多数情况下 `env` 字段会是 `ManagerBasedEnvCfg` 的子类。 +## 核心概念 -## apply_overrides +`TaskCfg` 是发现层与运行层之间的边界。它记录 task id、已注册 env 和 robot 名、默认 env 配置、 +可选 play-mode env 配置、可选 agent 配置。 -核心函数是 `apply_overrides(cfg, dict)`,解析点路径并把值应用到配置树: - -```python -from genelab.configs import apply_overrides - -apply_overrides(cfg, { - "env.simulation.dt": "0.005", - "env.simulation.steps": "500", - "env.robot.actuators.cart.stiffness": "100.0", - "env.observations.include_velocity": "true", -}) +```text +TaskCfg +├── env +├── play_env +└── agent ``` -### 类型转换 +`env` 有意标成 `object`。下游项目可以使用 `ManagerBasedRlEnvCfg` 这样的子类,而不需要修改 GeneLab core。 -字符串值按目标 dataclass 字段的类型注解自动转换: +## 为什么 dotted override 可行 -| 类型注解 | 接受的字符串 | -|---------|-------------| -| `bool` | `true`/`false`/`1`/`0`/`yes`/`no`(大小写不敏感) | -| `int` | 任意 10 进制整数字面量 | -| `float` | 任意 Python float 字面量 | -| `Path` | 经过 `pathlib.Path(...)` | -| `list[T]` | 逗号分隔,每个元素按 `T` 转换 | -| `tuple[T, ...]` | 逗号分隔,每个元素按 `T` 转换 | -| `str` | 原样 | +CLI 把 `--a.b.c VALUE` 当作 dataclass 树上的路径。`apply_overrides` 解析路径、读取当前值和类型注解、转换字符串值,并在运行前修改配置。 -当点路径无法解析到已知字段,或值无法转换时,`apply_overrides` 在配置构造期就抛出明确错误, -避免错误延后到仿真运行时才暴露。 +这样实验可复现:最终 `TaskCfg` 可以被 dump,非法路径会早失败,而不是被静默忽略。 -### CLI 转发路径 +## Play 配置是一等配置 -`play` / `train` 把每个 `-- VALUE` 标志转发给 `apply_overrides`。三个仿真短标志 -(`--vis`、`--gpu`、`--steps`)在转发前被改写为 `env.simulation.{vis,gpu,steps}`。 +训练和人工检查通常需要不同默认值。`play_env` 让 task 把 viewer 友好的设置和训练设置放在一起: +更少 env、打开 viewer、可选鼠标交互、可选 recording 输出。 -## See also +## 继续阅读 -- [执行器](actuators.md) -- [play 与 train](../cli/play-train.md) -- [API 参考](../api/reference.md) +- [配置参考](../reference/configuration.md) +- [设计任务](../best-practices/task-design.md) +- [CLI 参考](../reference/cli.md) diff --git a/docs/concepts/extensions.en.md b/docs/concepts/extensions.en.md index 13999d62..7732b9ea 100644 --- a/docs/concepts/extensions.en.md +++ b/docs/concepts/extensions.en.md @@ -1,65 +1,32 @@ # Extensions -GeneLab core ships **no** robots, environments, or tasks. All content lives in downstream -extension packages. The CLI discovers extensions through three pathways, in order of -preference. +Extensions are ordinary Python packages that register robots, environments, and tasks with GeneLab. +They are the expected way to build real projects. -## 1. Entry-point auto-discovery (recommended) +## Why extensions are separate -Declare in the extension's `pyproject.toml`: +Keeping downstream projects outside `src/genelab/` avoids turning the framework into a collection of +project-specific code. It also lets teams version, install, and publish their robot packages +independently. -```toml -[project.entry-points."genelab.extensions"] -my_robot_project = "my_robot_project:register" -``` +## Discovery mechanisms -The CLI auto-imports every entry point in the `genelab.extensions` group on startup. -`register()` is a no-argument callable at the package top level that performs the actual -`ROBOTS.register(...)` / `ENVS.register(...)` / `TASKS.register(...)` calls (or simply imports -modules that perform them as a side effect). +| Mechanism | Best use | +|---|---| +| Entry point in `genelab.extensions` | Installed packages and daily workflows. | +| CLI `--import MODULE` | Temporary local modules or debugging entry-point loading. | +| Programmatic loading | Embedded applications. | -This is the pathway `genelab project new` wires up automatically. +All mechanisms end with the same operation: a registration function calls `register_robot`, +`register_env`, and `register_task`. -## 2. Explicit `--import` +## Extension contract -```bash -uv run genelab --import my_pkg.module1 --import my_pkg.module2 list tasks -``` +An extension should be importable as a package, keep registry-time imports light, expose a +no-argument `register()` hook, and avoid duplicate registration in repeated loads. -Repeatable. Common uses: +## Where to continue -- The extension does not (yet) ship a `genelab.extensions` entry point. -- A fully explicit, reproducible loading order is required (combine with `--no-entry-points`). -- Iterating on a not-yet-installed package while ad-hoc-adding its source directory to - `sys.path` (the CLI also adds the current working directory to `sys.path`). - -## 3. Programmatic - -```python -from genelab.registry import load_extension_module - -load_extension_module("my_pkg.module") -``` - -Used by tests and embedding scripts. The same idempotency guard applies, so calling it after -the CLI has already auto-discovered the same module is a no-op. - -## Disabling auto-discovery - -```bash -uv run genelab --no-entry-points --import my_pkg list tasks -``` - -This is the most reproducible setup — only the modules explicitly named are loaded. - -## Reference extension - -`examples/genelab_examples/` is the reference shape: a `pyproject.toml` with the entry point, a -top-level `register()` callable, and `config.py` / `robots.py` / `envs.py` / `tasks.py` -modules. `tests/fake_extension.py` is the same shape stripped to bare minimum for the test -suite. - -## See also - -- [Project new](../cli/project-new.md) +- [Build an Extension Project](../best-practices/extension-projects.md) +- [Project New](../cli/project-new.md) - [Registry](registry.md) diff --git a/docs/concepts/extensions.zh.md b/docs/concepts/extensions.zh.md index beebfb35..e18c295f 100644 --- a/docs/concepts/extensions.zh.md +++ b/docs/concepts/extensions.zh.md @@ -1,61 +1,27 @@ # 扩展加载 -GeneLab 核心**不**自带机器人、环境、任务,所有内容由下游扩展包提供。CLI 通过三条路径发现 -扩展,按以下优先级。 +扩展是普通 Python 包,用来向 GeneLab 注册机器人、环境和任务。真实项目应使用这种方式构建。 -## 1. Entry-point 自动发现(推荐) +## 为什么扩展要分离 -在扩展的 `pyproject.toml` 中声明: +把下游项目放在 `src/genelab/` 外,可以避免框架变成项目专用代码集合。团队也能独立 version、install 和发布自己的机器人包。 -```toml -[project.entry-points."genelab.extensions"] -my_robot_project = "my_robot_project:register" -``` +## 发现机制 -CLI 启动时自动 import `genelab.extensions` 组下的每个 entry point。`register()` 是一个无参 -可调用,位于扩展包顶层,负责真正调用 `ROBOTS.register(...)` / `ENVS.register(...)` / -`TASKS.register(...)`(或仅 import 一些以副作用方式注册的模块)。 +| 机制 | 适用场景 | +|---|---| +| `genelab.extensions` entry point | 已安装包和日常工作流。 | +| CLI `--import MODULE` | 临时本地模块或调试 entry-point 加载。 | +| 程序内加载 | 嵌入式应用。 | -这条路径正是 `genelab project new` 默认接好的。 +所有机制最终做同一件事:注册函数调用 `register_robot`、`register_env`、`register_task`。 -## 2. 显式 `--import` +## 扩展契约 -```bash -uv run genelab --import my_pkg.module1 --import my_pkg.module2 list tasks -``` +扩展应能作为包导入,保持注册阶段导入轻量,暴露无参 `register()` hook,并避免重复加载时重复注册。 -可重复。常见场景: - -- 扩展尚未提供 `genelab.extensions` entry point。 -- 需要完全显式、可复现的加载顺序(搭配 `--no-entry-points`)。 -- 调试尚未安装的包,临时把源码目录塞进 `sys.path`(CLI 也会把当前工作目录加入 `sys.path`)。 - -## 3. 程序内调用 - -```python -from genelab.registry import load_extension_module - -load_extension_module("my_pkg.module") -``` - -测试与嵌入脚本使用。同样的幂等守卫生效,即使 CLI 已经自动发现过同一模块,再调用一次也是 -no-op。 - -## 关闭自动发现 - -```bash -uv run genelab --no-entry-points --import my_pkg list tasks -``` - -这是最可复现的组合 —— 只加载显式命名的模块。 - -## 参考扩展 - -`examples/genelab_examples/` 是参考形态:`pyproject.toml` 带 entry point、顶层有 -`register()` 可调用、并提供 `config.py` / `robots.py` / `envs.py` / `tasks.py`。 -`tests/fake_extension.py` 是同样形态但削减到最小,供测试套件使用。 - -## See also +## 继续阅读 +- [构建扩展项目](../best-practices/extension-projects.md) - [新建项目](../cli/project-new.md) - [注册表](registry.md) diff --git a/docs/concepts/managers.en.md b/docs/concepts/managers.en.md index 21cbecd7..55beea69 100644 --- a/docs/concepts/managers.en.md +++ b/docs/concepts/managers.en.md @@ -1,274 +1,54 @@ -# Managers and MDP terms +# Managers and MDP Terms -`genelab.managers` exposes the seven manager-style MDP hooks that -`ManagerBasedRlEnv` orchestrates: actions, commands, observations, rewards, -terminations, events, and curriculums. Each manager owns a dict of *term* cfgs -keyed by name; the env constructs them in a fixed order, then drives them once -per control step or once per reset. +GeneLab follows the Isaac Lab-style manager-based environment pattern: the environment owns the +simulation loop, while MDP behavior is split into named terms managed by specialized managers. -## Term and manager +## Why split the MDP -A *term* is a single MDP-side computation — one reward component, one -observation column group, one termination check. A *manager* is the container -that instantiates all terms of one kind, exposes their aggregate output, and -schedules their per-step / per-reset calls. +A robot learning environment usually contains many concerns: -``` -ManagerBasedRlEnvCfg -├── actions_cfg: dict[str, ActionTermCfg] -├── commands_cfg: dict[str, CommandTermCfg] -├── observations_cfg: dict[str, ObservationGroupCfg] # group → terms -├── rewards_cfg: dict[str, RewardTermCfg] -├── terminations_cfg: dict[str, TerminationTermCfg] -├── events_cfg: dict[str, EventTermCfg] -└── curriculum_cfg: dict[str, CurriculumTermCfg] -``` +- action decoding +- command sampling +- observations +- rewards +- terminations +- reset and interval events +- curricula +- metrics -Every term cfg inherits `ManagerTermBaseCfg` with two fields: +Putting all of that directly in `step()` makes tasks hard to inspect and override. Manager-based +configs keep each concern named, typed, and discoverable. -| Field | Type | Meaning | -|---|---|---| -| `func` | `Callable[..., Any]` | The per-step computation. For observation / reward / termination / event / curriculum terms this is a function taking `(env, **params)` and returning a tensor (or `None` for events). Action and command terms ignore `func` and use `class_type` instead. | -| `params` | `dict[str, Any]` | Keyword arguments injected on every call. `apply_overrides` can mutate individual entries via dotted paths. | - -## The seven managers - -| Manager | Term cfg | Term identifier | Call schedule | -|---|---|---|---| -| `ActionManager` | `ActionTermCfg(class_type=…)` | `class_type` | `process_action` once per env step; `apply_action` `decimation` times per env step | -| `CommandManager` | `CommandTermCfg(class_type=…)` | `class_type` | `compute(dt)` once per env step (resampling on countdown) | -| `ObservationManager` | `ObservationGroupCfg.terms[name] = ObservationTermCfg(func=…)` | `func` | `compute()` once per env step (after reset and after step) | -| `RewardManager` | `RewardTermCfg(func=…, weight=…)` | `func` | `compute(dt)` once per env step | -| `TerminationManager` | `TerminationTermCfg(func=…, time_out=…)` | `func` | `compute()` once per env step | -| `EventManager` | `EventTermCfg(func=…, mode=…)` | `func` | once per construct (`startup`), once per reset (`reset`), or on per-env countdown (`interval`) | -| `CurriculumManager` | `CurriculumTermCfg(func=…)` | `func` | `compute(env_ids)` once per reset, after the other managers reset | - -## Term lifecycle - -`ManagerBasedRlEnv.__init__` constructs the seven managers in the order above. -For each term: - -1. The manager deep-copies the cfg dict so per-instance mutation never bleeds - back into the user's source cfg. -2. If `func` is a class, `_base.instantiate_class_term` replaces it with - `func(cfg=term_cfg, env=env)` so the term can cache references. Plain - callables are kept as-is. -3. Action and command managers instead invoke `class_type(term_cfg, env)` and - keep the resulting instance under the term name. - -After all managers are constructed the env calls `event_manager.apply("startup")` -once, then `articulation.refresh()`, then `reset()` for every env. - -## ActionManager - -Each `ActionTermCfg` declares a `class_type` (an `ActionTerm` subclass) and an -optional `asset_name`. The manager concatenates per-term `action_dim` slots -into a single flat action vector. Multiple action terms can coexist, e.g. one -covering the arm joints and one covering the gripper: - -```python -from genelab.mdp.actions.joint_position import JointPositionActionCfg - -actions_cfg = { - "panda_arm": JointPositionActionCfg( - asset_name="robot", - joint_names=(r"^joint[1-7]$",), - use_default_offset=True, - ), - "panda_hand": JointPositionActionCfg( - asset_name="robot", - joint_names=(r"finger_joint.*",), - use_default_offset=True, - ), -} -``` - -`process_action(action)` runs once per env step; `apply_action()` is called -`decimation` times per env step before each sim sub-step, so the controller -sees a fixed setpoint for the whole decimation window. - -## ObservationManager - -Observations are organised into named *groups* (`policy`, `critic`, …); each -group is a dict of named terms. - -```python -from genelab.managers import ObservationGroupCfg, ObservationTermCfg -from genelab import mdp - -observations_cfg = { - "policy": ObservationGroupCfg( - terms={ - "base_lin_vel": ObservationTermCfg(func=mdp.base_lin_vel), - "joint_pos_rel": ObservationTermCfg(func=mdp.joint_pos_rel), - "last_action": ObservationTermCfg(func=mdp.last_action), - }, - concatenate_terms=True, - enable_corruption=False, - ), -} -``` - -For each term `compute()` calls `term_cfg.func(env, **term_cfg.params)`, -unsqueezes 1-D returns to `(num_envs, 1)`, then optionally applies `noise` -(only when `enable_corruption=True`), `scale`, and `clip`. When -`concatenate_terms=True` (default) the per-group output is a single -`(num_envs, total_dim)` tensor; otherwise the manager stacks along a new last -axis. - -## RewardManager - -`RewardTermCfg.weight` is the signed scalar multiplier. The manager skips -zero-weight terms. When `ManagerBasedRlEnvCfg.scale_rewards_by_dt=True` -(default) the per-term tensor is also multiplied by the env step `dt`, so total -episode return is comparable across simulation frequencies. - -```python -from genelab.managers import RewardTermCfg -from genelab import mdp +## The manager set -rewards_cfg = { - "track_lin_vel": RewardTermCfg( - func=mdp.track_linear_velocity_xy_exp, - weight=1.0, - params={"command_name": "base_velocity", "std": 0.25}, - ), - "action_rate": RewardTermCfg(func=mdp.action_rate_l2, weight=-0.005), - "joint_acc": RewardTermCfg(func=mdp.joint_acc_l2, weight=-2.5e-7), -} -``` - -Per-term `weight * func(...)` outputs are NaN-coerced, summed, and exposed as a -single `(num_envs,)` reward tensor. The manager also accumulates a per-term -episode sum; `reset()` returns the mean episode reward per term (logged as -`Episode_Reward/` in `extras["log"]`). - -## TerminationManager - -Each `TerminationTermCfg.func` must return a bool tensor of shape -`(num_envs,)`. Terms with `time_out=True` flow into the truncation buffer -(`info["time_out"]`); the rest accumulate into the terminated buffer. Both are -OR'd to produce the final `dones` mask. RSL-RL distinguishes the two so the -PPO update treats truncation correctly. - -```python -from genelab.managers import TerminationTermCfg -from genelab import mdp - -terminations_cfg = { - "time_out": TerminationTermCfg(func=mdp.time_out, time_out=True), - "fell_over": TerminationTermCfg( - func=mdp.bad_orientation, - params={"limit_angle": 1.0}, - ), -} -``` - -## EventManager - -`EventTermCfg.mode` is a `Literal["startup", "reset", "interval"]`: - -| Mode | Fires | -|---|---| -| `startup` | Once, right after all managers are constructed. Useful for one-shot randomisation of physical parameters (mass, friction). | -| `reset` | On every env reset, before `command_manager.reset` and `action_manager.reset`. Use this to randomise initial joint state, root pose, or env-specific buffers. | -| `interval` | Per-env countdown sampled uniformly from `interval_range_s`. Fires inside the env step when the countdown hits zero, then resamples. Use for periodic disturbances (random push). | - -```python -from genelab.managers import EventTermCfg -from genelab import mdp - -events_cfg = { - "reset_joints": EventTermCfg( - mode="reset", - func=mdp.reset_joints_to_default, - params={"pos_jitter": 0.05, "vel_jitter": 0.0}, - ), - "push_robot": EventTermCfg( - mode="interval", - func=mdp.push_by_setting_velocity, - interval_range_s=(8.0, 12.0), - params={"velocity_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5)}}, - ), -} -``` - -## CommandManager - -A *command* is a persistent per-env target (velocity command, motion -reference, goal pose) that survives across env steps until it is resampled. -Each `CommandTermCfg.class_type` is a `CommandTerm` subclass; the term holds -the live tensor and resamples it every `resampling_time_range` seconds. -Observation and reward terms read commands back via -`env.command_manager.get_command(name)`. - -```python -from genelab.mdp.commands.uniform_velocity import UniformVelocityCommandCfg - -commands_cfg = { - "base_velocity": UniformVelocityCommandCfg( - asset_name="robot", - resampling_time_range=(10.0, 10.0), - ranges=UniformVelocityCommandCfg.Ranges( - lin_vel_x=(-1.0, 1.0), lin_vel_y=(-0.5, 0.5), ang_vel_z=(-1.0, 1.0), - ), - ), -} -``` - -## CurriculumManager - -Curriculum terms run on every reset (after the other managers have reset) -and may mutate scene state — `TerrainImporter.terrain_levels`, env-specific -spawn poses, reward weights. The return value is logged as -`Curriculum/` in `extras["log"]`; tensor returns are reduced to their -mean across `env_ids`. - -```python -from genelab.managers import CurriculumTermCfg -from genelab.mdp.curriculums import terrain_levels_vel - -curriculum_cfg = { - "terrain_levels": CurriculumTermCfg( - func=terrain_levels_vel, - params={ - "command_name": "base_velocity", - "distance_threshold": 5.0, - "demote_ratio": 0.5, - }, - ), -} -``` +| Manager | Config field | Role | +|---|---|---| +| `ActionManager` | `actions_cfg` | Converts policy actions into simulator targets or torques. | +| `CommandManager` | `commands_cfg` | Maintains sampled goals such as target velocity or reference motion. | +| `ObservationManager` | `observations_cfg` | Computes named observation groups such as `policy` and `critic`. | +| `RewardManager` | `rewards_cfg` | Computes weighted reward terms and episode summaries. | +| `TerminationManager` | `terminations_cfg` | Separates terminated and time-out conditions. | +| `EventManager` | `events_cfg` | Runs startup, reset, and interval randomization or perturbation. | +| `CurriculumManager` | `curriculum_cfg` | Adjusts difficulty or task state at reset boundaries. | +| `MetricsManager` | `metrics_cfg` | Records non-reward diagnostics. | -## Failure modes worth knowing +## Runtime order -!!! warning "Wrong observation tensor shape" - `ObservationManager.compute` only auto-unsqueezes 1-D returns to - `(num_envs, 1)`. A scalar-per-env reward term that accidentally returns a - `()` scalar will broadcast in the wrong direction. Always return shape - `(num_envs,)` or `(num_envs, d)`. +`ManagerBasedRlEnv` builds the Genesis scene first, then creates managers, binds sensors, applies +startup events, and runs an initial reset. During each env step it processes actions, advances the +scene for `decimation` physics ticks, refreshes state, updates sensors, computes commands, events, +rewards, metrics, terminations, resets done envs, and finally computes observations. -!!! warning "Missing class_type / func" - `ActionManager` and `CommandManager` silently skip terms whose `class_type` - is `None`. `RewardManager` / `TerminationManager` / `EventManager` / - `CurriculumManager` keep the no-op default `func` from `ManagerTermBaseCfg`, - so a missing `func=` produces a manager that returns `None` and crashes - downstream when the result is used as a tensor. Always set the term - factory explicitly. +This order matters because terms read shared state. For example, reward terms see the state after +actions and sensor updates; reset events run before the next observation is emitted. -!!! warning "Interval events without `interval_range_s`" - `EventManager.__init__` asserts that every `mode="interval"` term provides - `interval_range_s=(low, high)`. The assertion fires at manager construction - so misconfigured tasks never reach the first reset. +## Term names are part of the interface -!!! tip "Override term params at the CLI" - Every term `params` entry is reachable via `apply_overrides`. The dotted - path mirrors the cfg tree printed by `genelab info `: - `--env.rewards_cfg.track_lin_vel.weight 2.0` rescales a reward term; - `--env.rewards_cfg.track_lin_vel.params.std 0.5` retargets a kwarg. +Term names appear in override paths and logs. A reward named `track_lin_vel` can be changed from the +CLI and appears in episode summaries. Stable names make experiments easier to compare. -## See also +## Where to continue -- [Configs](configs.md) -- [Sensors](sensors.md) -- [Discovery: list and info](../cli/list-info.md) +- [Design a Task](../best-practices/task-design.md) +- [MDP terms reference](mdp.md) +- [API Reference](../api/reference.md) diff --git a/docs/concepts/managers.zh.md b/docs/concepts/managers.zh.md index 8533bc09..07199b04 100644 --- a/docs/concepts/managers.zh.md +++ b/docs/concepts/managers.zh.md @@ -1,251 +1,51 @@ # Manager 与 MDP term -`genelab.managers` 暴露了 `ManagerBasedRlEnv` 编排的七个 manager 风格 MDP 钩子: -actions、commands、observations、rewards、terminations、events、curriculums。 -每个 manager 持有一个按名键入的 term cfg 字典;env 按固定顺序构造它们,再按每控制 -步或每次 reset 的节奏驱动。 +GeneLab 使用 Isaac Lab 风格的 manager-based 环境模式:环境负责仿真循环,MDP 行为拆成命名 term, +由专门 manager 管理。 -## term 与 manager +## 为什么拆分 MDP -*term* 是单条 MDP 侧的计算 —— 一个 reward 分量、一组 observation 列、一个终止判定。 -*manager* 是容器,负责实例化某一类 term、暴露聚合输出、并按每步 / 每次 reset 调度。 +机器人学习环境通常包含很多关注点: -``` -ManagerBasedRlEnvCfg -├── actions_cfg: dict[str, ActionTermCfg] -├── commands_cfg: dict[str, CommandTermCfg] -├── observations_cfg: dict[str, ObservationGroupCfg] # group → terms -├── rewards_cfg: dict[str, RewardTermCfg] -├── terminations_cfg: dict[str, TerminationTermCfg] -├── events_cfg: dict[str, EventTermCfg] -└── curriculum_cfg: dict[str, CurriculumTermCfg] -``` +- action 解码 +- command 采样 +- observation +- reward +- termination +- reset 和 interval event +- curriculum +- metric -所有 term cfg 都继承 `ManagerTermBaseCfg`,含两个字段: +如果全部塞进 `step()`,任务会很难检查和 override。manager-based 配置让每个关注点都有名字、类型和可发现路径。 -| 字段 | 类型 | 含义 | -|---|---|---| -| `func` | `Callable[..., Any]` | 每步执行的计算。observation / reward / termination / event / curriculum 类 term 的 `func` 签名为 `(env, **params)` 并返回 tensor(event 可返回 `None`)。action 与 command term 不使用 `func`,改用 `class_type`。 | -| `params` | `dict[str, Any]` | 每次调用注入的关键字参数。`apply_overrides` 可按 dotted path 改写其中任一条目。 | - -## 七个 manager - -| Manager | Term cfg | term 识别字段 | 调用节奏 | -|---|---|---|---| -| `ActionManager` | `ActionTermCfg(class_type=…)` | `class_type` | 每 env step 一次 `process_action`;每 env step 内 `apply_action` 触发 `decimation` 次 | -| `CommandManager` | `CommandTermCfg(class_type=…)` | `class_type` | 每 env step 一次 `compute(dt)`(按倒计时重采样) | -| `ObservationManager` | `ObservationGroupCfg.terms[name] = ObservationTermCfg(func=…)` | `func` | 每 env step 一次 `compute()`(reset 后与 step 后各一次) | -| `RewardManager` | `RewardTermCfg(func=…, weight=…)` | `func` | 每 env step 一次 `compute(dt)` | -| `TerminationManager` | `TerminationTermCfg(func=…, time_out=…)` | `func` | 每 env step 一次 `compute()` | -| `EventManager` | `EventTermCfg(func=…, mode=…)` | `func` | 构造时一次(`startup`)、每次 reset 一次(`reset`)、按每 env 倒计时触发(`interval`) | -| `CurriculumManager` | `CurriculumTermCfg(func=…)` | `func` | 每次 reset 一次 `compute(env_ids)`,在其他 manager reset 之后 | - -## term 生命周期 - -`ManagerBasedRlEnv.__init__` 按上表顺序构造七个 manager。每个 term 的处理: - -1. Manager 深拷贝 cfg 字典,避免逐实例 mutation 反向污染源 cfg。 -2. 当 `func` 是类时,`_base.instantiate_class_term` 把它替换为 - `func(cfg=term_cfg, env=env)`,让 term 在构造期缓存引用。普通可调用对象保持原样。 -3. action / command manager 改用 `class_type(term_cfg, env)`,把得到的实例按 term 名存好。 - -七个 manager 全部构造完毕后,env 调用一次 `event_manager.apply("startup")`, -再 `articulation.refresh()`,最后对所有 env 跑一次 `reset()`。 - -## ActionManager - -每个 `ActionTermCfg` 声明一个 `class_type`(`ActionTerm` 子类)与可选的 `asset_name`。 -Manager 把每个 term 的 `action_dim` 拼成单个扁平 action 向量。多个 action term -可以并存 —— 例如手臂一个、夹爪一个: - -```python -from genelab.mdp.actions.joint_position import JointPositionActionCfg - -actions_cfg = { - "panda_arm": JointPositionActionCfg( - asset_name="robot", - joint_names=(r"^joint[1-7]$",), - use_default_offset=True, - ), - "panda_hand": JointPositionActionCfg( - asset_name="robot", - joint_names=(r"finger_joint.*",), - use_default_offset=True, - ), -} -``` - -`process_action(action)` 在每 env step 调用一次;`apply_action()` 在每 env step 内 -触发 `decimation` 次(每个 sim 子步前一次),因此控制器在整个 decimation 窗口内 -看到的是同一个目标。 - -## ObservationManager - -Observation 按命名 *group*(`policy` / `critic` / …)组织;每个 group 是命名 term 字典。 - -```python -from genelab.managers import ObservationGroupCfg, ObservationTermCfg -from genelab import mdp - -observations_cfg = { - "policy": ObservationGroupCfg( - terms={ - "base_lin_vel": ObservationTermCfg(func=mdp.base_lin_vel), - "joint_pos_rel": ObservationTermCfg(func=mdp.joint_pos_rel), - "last_action": ObservationTermCfg(func=mdp.last_action), - }, - concatenate_terms=True, - enable_corruption=False, - ), -} -``` - -`compute()` 对每个 term 调用 `term_cfg.func(env, **term_cfg.params)`,1-D 返回会被 -自动 unsqueeze 成 `(num_envs, 1)`,随后按需 apply `noise`(仅当 -`enable_corruption=True`)、`scale`、`clip`。`concatenate_terms=True`(默认)时整组 -输出是单张 `(num_envs, total_dim)` tensor;否则 manager 沿新末轴 stack。 - -## RewardManager - -`RewardTermCfg.weight` 是带符号标量乘子。Manager 跳过 weight=0 的 term。当 -`ManagerBasedRlEnvCfg.scale_rewards_by_dt=True`(默认)时,逐 term tensor 还会再乘 -env step `dt`,使得 episode 总回报在不同仿真频率下可比。 - -```python -from genelab.managers import RewardTermCfg -from genelab import mdp +## manager 集合 -rewards_cfg = { - "track_lin_vel": RewardTermCfg( - func=mdp.track_linear_velocity_xy_exp, - weight=1.0, - params={"command_name": "base_velocity", "std": 0.25}, - ), - "action_rate": RewardTermCfg(func=mdp.action_rate_l2, weight=-0.005), - "joint_acc": RewardTermCfg(func=mdp.joint_acc_l2, weight=-2.5e-7), -} -``` - -逐 term 的 `weight * func(...)` 结果会 NaN coerce、求和,并作为 `(num_envs,)` 的 -reward tensor 输出。Manager 同时累计每 term 的 episode 求和;`reset()` 返回每 -term 的 episode 平均奖励(按 `Episode_Reward/` 写入 `extras["log"]`)。 - -## TerminationManager - -每个 `TerminationTermCfg.func` 必须返回 `(num_envs,)` 的 bool tensor。`time_out=True` -的 term 写入 truncation buffer(`info["time_out"]`),其余写入 terminated buffer。 -两者 OR 后得到最终 `dones`。RSL-RL 区分这两者以在 PPO 更新中正确处理截断。 - -```python -from genelab.managers import TerminationTermCfg -from genelab import mdp - -terminations_cfg = { - "time_out": TerminationTermCfg(func=mdp.time_out, time_out=True), - "fell_over": TerminationTermCfg( - func=mdp.bad_orientation, - params={"limit_angle": 1.0}, - ), -} -``` - -## EventManager - -`EventTermCfg.mode` 是 `Literal["startup", "reset", "interval"]`: - -| Mode | 触发时机 | -|---|---| -| `startup` | 全部 manager 构造完成后一次。适合一次性随机化物理参数(质量、摩擦)。 | -| `reset` | 每次 env reset,在 `command_manager.reset` 与 `action_manager.reset` 之前。适合随机化初始 joint state、root pose、env 缓存。 | -| `interval` | 按每 env 倒计时(从 `interval_range_s` 均匀采样)。在 env step 内倒计时归零时触发,触发后重新采样。适合周期性扰动(随机推力)。 | - -```python -from genelab.managers import EventTermCfg -from genelab import mdp - -events_cfg = { - "reset_joints": EventTermCfg( - mode="reset", - func=mdp.reset_joints_to_default, - params={"pos_jitter": 0.05, "vel_jitter": 0.0}, - ), - "push_robot": EventTermCfg( - mode="interval", - func=mdp.push_by_setting_velocity, - interval_range_s=(8.0, 12.0), - params={"velocity_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5)}}, - ), -} -``` - -## CommandManager - -*command* 是按 env 持久化的目标(速度命令、动作参考、目标位姿),跨 env step 保留, -直到被重采样。每个 `CommandTermCfg.class_type` 是 `CommandTerm` 子类;term 持有 -当前 tensor 并每 `resampling_time_range` 秒重采样一次。observation 与 reward term -通过 `env.command_manager.get_command(name)` 读回。 - -```python -from genelab.mdp.commands.uniform_velocity import UniformVelocityCommandCfg - -commands_cfg = { - "base_velocity": UniformVelocityCommandCfg( - asset_name="robot", - resampling_time_range=(10.0, 10.0), - ranges=UniformVelocityCommandCfg.Ranges( - lin_vel_x=(-1.0, 1.0), lin_vel_y=(-0.5, 0.5), ang_vel_z=(-1.0, 1.0), - ), - ), -} -``` - -## CurriculumManager - -Curriculum term 在每次 reset 触发(在其他 manager reset 之后),可改写 scene 状态 —— -`TerrainImporter.terrain_levels`、env 专属 spawn pose、reward weight。返回值按 -`Curriculum/` 写入 `extras["log"]`;tensor 返回会被 reduce 成在 `env_ids` 上的均值。 - -```python -from genelab.managers import CurriculumTermCfg -from genelab.mdp.curriculums import terrain_levels_vel - -curriculum_cfg = { - "terrain_levels": CurriculumTermCfg( - func=terrain_levels_vel, - params={ - "command_name": "base_velocity", - "distance_threshold": 5.0, - "demote_ratio": 0.5, - }, - ), -} -``` +| Manager | 配置字段 | 作用 | +|---|---|---| +| `ActionManager` | `actions_cfg` | 把策略 action 转成仿真目标或力矩。 | +| `CommandManager` | `commands_cfg` | 维护采样目标,例如目标速度或参考 motion。 | +| `ObservationManager` | `observations_cfg` | 计算 `policy`、`critic` 等 observation group。 | +| `RewardManager` | `rewards_cfg` | 计算带权 reward term 和 episode 摘要。 | +| `TerminationManager` | `terminations_cfg` | 区分 terminated 和 time-out 条件。 | +| `EventManager` | `events_cfg` | 执行 startup、reset、interval 随机化或扰动。 | +| `CurriculumManager` | `curriculum_cfg` | 在 reset 边界调整难度或任务状态。 | +| `MetricsManager` | `metrics_cfg` | 记录非 reward 诊断指标。 | -## 已知失效模式 +## 运行时顺序 -!!! warning "observation tensor 形状错误" - `ObservationManager.compute` 只会把 1-D 返回 auto-unsqueeze 成 `(num_envs, 1)`。 - 意外返回 `()` 标量的 reward term 会朝错误方向广播。term 必须返回 `(num_envs,)` - 或 `(num_envs, d)`。 +`ManagerBasedRlEnv` 先构建 Genesis scene,再创建 manager、绑定传感器、执行 startup event、 +跑初始 reset。每个 env step 中,它处理 action,推进 `decimation` 个 physics tick,刷新状态, +更新传感器,计算 command、event、reward、metric、termination,reset 已完成 env,最后计算 observation。 -!!! warning "缺失 class_type / func" - `ActionManager` 与 `CommandManager` 会静默跳过 `class_type=None` 的 term。 - `RewardManager` / `TerminationManager` / `EventManager` / `CurriculumManager` - 保留 `ManagerTermBaseCfg` 的 no-op 默认 `func`,缺失 `func=` 会构造出返回 `None` - 的 manager,并在下游把结果当 tensor 用时崩溃。term factory 必须显式设置。 +这个顺序很重要,因为 term 读取共享状态。例如 reward term 看到的是 action 和 sensor update 之后的状态; +reset event 会在下一次 observation 发出前运行。 -!!! warning "interval event 缺 `interval_range_s`" - `EventManager.__init__` 断言每个 `mode="interval"` term 必须给 `interval_range_s=(low, high)`。 - 断言在 manager 构造时触发,错配的 task 永远跑不到第一次 reset。 +## term 名称是接口的一部分 -!!! tip "在 CLI 覆盖 term 参数" - 每个 term 的 `params` 条目都可通过 `apply_overrides` 访问。dotted path 与 - `genelab info ` 打印的 cfg 树一一对应: - `--env.rewards_cfg.track_lin_vel.weight 2.0` 重新缩放一个 reward term; - `--env.rewards_cfg.track_lin_vel.params.std 0.5` 改写一个 kwarg。 +term 名会出现在 override 路径和日志中。名为 `track_lin_vel` 的 reward 可以从 CLI 修改,也会出现在 episode 摘要里。稳定命名让实验更容易比较。 -## See also +## 继续阅读 -- [Configs](configs.md) -- [Sensors](sensors.md) -- [Discovery: list and info](../cli/list-info.md) +- [设计任务](../best-practices/task-design.md) +- [MDP term 参考](mdp.md) +- [API 参考](../api/reference.md) diff --git a/docs/concepts/mdp.en.md b/docs/concepts/mdp.en.md index 9b6b7042..1d6cad68 100644 --- a/docs/concepts/mdp.en.md +++ b/docs/concepts/mdp.en.md @@ -1,281 +1,49 @@ -# MDP terms reference +# MDP Terms Reference -`genelab.mdp` is a library of plain Python callables that plug straight into -the seven managers. Every function follows the same signature shape — `(env, -**params) -> torch.Tensor` for observation / reward / termination, `(env, -env_ids, **params) -> None` for events, `(env, env_ids, **params) -> Tensor` -for curriculums — and is referenced by the corresponding `*TermCfg.func` (or -`class_type`) field. +`genelab.mdp` is a reusable term library. It does not define a task by itself; task configs select +functions and classes from this library and wire them into managers. -## Reusable building blocks +## Actions and commands -Every name listed below is re-exported by `genelab.mdp.__init__`, so -`from genelab import mdp` is enough to reach them. The full module map: +| Area | Public pieces | +|---|---| +| Actions | `JointPositionActionCfg`, `JointPositionAction` | +| Velocity commands | `UniformVelocityCommandCfg`, `UniformVelocityCommand` | +| Motion commands | `MotionCommandCfg`, `MotionCommand`, `MotionLoader` | -``` -genelab.mdp.actions → JointPositionAction(Cfg) -genelab.mdp.commands → UniformVelocityCommand(Cfg), MotionCommand(Cfg), MotionLoader -genelab.mdp.observations → 16 observation functions -genelab.mdp.rewards → 15 reward functions / classes -genelab.mdp.terminations → 6 termination functions -genelab.mdp.events → 3 event functions -genelab.mdp.curriculums → 1 curriculum function -genelab.mdp.noise → NoiseCfg base + Unoise + Gnoise -``` - -The output-shape column below uses `B = num_envs`, `D = action / joint / -sensor dimension`, `N = number of bodies or feet`. Reward and termination -terms always return shape `(B,)`; observation terms return `(B, D)` (or are -auto-unsqueezed from `(B,)` by `ObservationManager`). - -## Actions - -| Name | Signature (Cfg fields used) | Behavior | -|---|---|---| -| `JointPositionAction` | `JointPositionActionCfg(joint_names, scale, use_default_offset, asset_name)` | `target = default + scale * raw_action`. Targets are dispatched via `Articulation.write_joint_targets_partial`, which routes each joint to its actuator group's declared channel (position for implicit PD, force for `IdealPDActuator` / `DCMotorActuator`). `scale=None` (default) inherits per-joint scale from `Articulation.action_scale_tensor`; a `float` or `dict[str, float]` overrides it. Patterns are regex; unmatched patterns raise. | - -```python -from genelab.mdp.actions.joint_position import JointPositionActionCfg - -actions_cfg = { - "panda_arm": JointPositionActionCfg( - asset_name="robot", - joint_names=(r"^joint[1-7]$",), - use_default_offset=True, - ), -} -``` - -## Commands - -| Name | Signature (Cfg fields used) | Behavior | -|---|---|---| -| `UniformVelocityCommand` | `UniformVelocityCommandCfg(ranges, rel_standing_envs, heading_command, heading_control_stiffness, resampling_time_range, asset_name)` | Body-frame `[lin_vel_x, lin_vel_y, ang_vel_z]` per env, resampled uniformly from `ranges` every `resampling_time_range` seconds. With `heading_command=True` (default), `ang_vel_z` is rewritten each step to drive the body toward a sampled heading. `rel_standing_envs` zeroes a fraction of resampled commands so the policy also sees a "stand still" target. `command` shape `(B, 3)`. | -| `MotionCommand` | `MotionCommandCfg(motion_file, anchor_body_name, body_names, motion_body_order, motion_joint_order, pose_range, velocity_range, sampling_mode, ...)` | Drives the env toward a recorded motion clip frame-by-frame. Reads the NPZ schema produced by mjlab's `csv_to_npz`. `motion_body_order` / `motion_joint_order` describe the NPZ's native body / joint axis ordering (typically mjlab's MJCF DFS) so `MotionLoader` can permute the data into the runtime robot's order — required when the source uses a different traversal than Genesis. Exposes anchor + multi-body reference and current pose / velocity for the relative-pose reward functions in the next section. `sampling_mode="start"` always begins at frame 0; `"uniform"` (default) samples a random frame. | -| `MotionLoader` | `MotionLoader(motion_file, body_indexes, device, joint_perm=None)` | Helper class consumed by `MotionCommand`. Loads the NPZ into device tensors, applies the optional `joint_perm` to the joint axis, and slices `body_pos_w / body_quat_w / body_lin_vel_w / body_ang_vel_w` down to the bodies the command tracks. Direct use is rare — instantiate `MotionCommandCfg` instead. | - -```python -from genelab.mdp.commands.velocity_command import UniformVelocityCommandCfg - -commands_cfg = { - "base_velocity": UniformVelocityCommandCfg( - asset_name="robot", - resampling_time_range=(10.0, 10.0), - ranges=UniformVelocityCommandCfg.Ranges( - lin_vel_x=(-1.0, 1.0), lin_vel_y=(-0.5, 0.5), ang_vel_z=(-1.0, 1.0), - ), - heading_command=True, - ), -} -``` +Actions convert policy outputs to simulator control. Commands hold sampled goals that observations +and rewards can read. ## Observations -All listed functions take `env` plus the noted kwargs and return a tensor of -shape `(B, D)` or `(B,)` (auto-unsqueezed to `(B, 1)` by the manager). - -| Name | Params | Shape | Source | -|---|---|---|---| -| `base_lin_vel` | — | `(B, 3)` | Body-frame floating-base linear velocity. | -| `base_ang_vel` | — | `(B, 3)` | Body-frame floating-base angular velocity. | -| `projected_gravity` | — | `(B, 3)` | World gravity projected into the body frame (IMU-orientation proxy). | -| `joint_pos_rel` | — | `(B, num_dofs)` | Joint positions minus the default pose. | -| `joint_vel_rel` | — | `(B, num_dofs)` | Joint velocities (default is zero — same as raw `joint_vel`). | -| `last_action` | — | `(B, total_action_dim)` | The action tensor the manager processed last step. | -| `generated_commands` | `command_name` | `(B, command_dim)` | `env.command_manager.get_command(command_name)`. | -| `sensor_data` | `sensor_name` | varies | Per-step cached tensor on `env.sensors[sensor_name]`. Use this for IMU, FrameTransformer, ray-cast outputs that are already tensors. | -| `foot_air_time` | `sensor_name` (must be a `ContactSensor`) | `(B, N_feet)` | Current per-foot air time (zero while in contact). | -| `foot_contact` | `sensor_name` (must be a `ContactSensor`) | `(B, N_feet)` | Binary contact mask as float. | -| `foot_contact_forces` | `sensor_name` (must be a `ContactSensor`) | `(B, N_feet * 3)` | Per-foot contact force, compressed via `sign(f) * log1p(|f|)` and flattened. | -| `height_scan` | `sensor_name` (must be a `TerrainHeightSensor`) | `(B, num_rays)` | Per-ray heights above the terrain. | -| `motion_anchor_pos_b` | `command_name` (must be a `MotionCommand`) | `(B, 3)` | Reference anchor position expressed in the robot's anchor frame. | -| `motion_anchor_ori_b` | `command_name` | `(B, 6)` | Reference 6D orientation (first two columns of the rotation matrix). | -| `robot_body_pos_b` | `command_name` | `(B, N_bodies * 3)` | Per-body positions in the robot's anchor frame (privileged critic obs). | -| `robot_body_ori_b` | `command_name` | `(B, N_bodies * 6)` | Per-body 6D orientations in the robot's anchor frame. | - -```python -from genelab.managers import ObservationGroupCfg, ObservationTermCfg -from genelab import mdp -from genelab.mdp.noise import Unoise - -observations_cfg = { - "policy": ObservationGroupCfg( - terms={ - "base_lin_vel": ObservationTermCfg(func=mdp.base_lin_vel, - noise=Unoise(n_min=-0.1, n_max=0.1)), - "joint_pos_rel": ObservationTermCfg(func=mdp.joint_pos_rel), - "last_action": ObservationTermCfg(func=mdp.last_action), - "commands": ObservationTermCfg(func=mdp.generated_commands, - params={"command_name": "base_velocity"}), - }, - enable_corruption=True, - ), -} -``` - -## Rewards - -All reward functions return shape `(B,)`. `RewardManager` multiplies by -`RewardTermCfg.weight` and (when `scale_rewards_by_dt=True`) by `dt`. Negative -weights turn a "deviation" reward into a penalty. - -| Name | Params | Behavior | -|---|---|---| -| `track_linear_velocity_xy_exp` | `command_name`, `std=0.5` | `exp(-||cmd_xy − vel_xy||² / std²)`. | -| `track_angular_velocity_z_exp` | `command_name`, `std=0.5` | `exp(-(cmd_z − vel_z)² / std²)`. | -| `action_rate_l2` | — | `Σ_d (action_d − prev_action_d)²` — penalize action jitter. | -| `joint_acc_l2` | — | **Placeholder** (currently returns zeros; awaits accel buffer). | -| `flat_orientation_l2` | — | `Σ (projected_gravity_xy)²` — penalize tilt. | -| `upright_exp` | `std=0.45` | `exp(-||projected_gravity_xy||² / std²)` — positive reward for upright base. | -| `variable_posture` | `command_name`, `std_standing` / `std_walking` / `std_running` (regex→float dicts), `default_std`, `walking_threshold`, `running_threshold` | Speed-dependent posture reward: `exp(-mean((joint_pos − default)² / std²))`, with std chosen per env from the command magnitude. Class-style term — instantiated once at construct, then `__call__` per step. | -| `joint_pos_limits` | — | L2 of joint-position excursion past ±π. | -| `feet_air_time` | `threshold=0.4` | **Stub** — reward proportional to mean foot-link height above ground, clamped at `threshold`. | -| `motion_global_anchor_position_error_exp` | `command_name`, `std` | `exp(-||p_ref − p_robot||² / std²)` on the anchor body in world frame. | -| `motion_global_anchor_orientation_error_exp` | `command_name`, `std` | Geodesic rotation error on the anchor body, mapped through a Gaussian kernel. | -| `motion_relative_body_position_error_exp` | `command_name`, `std`, `body_names=None` | Multi-body L2 position error against anchor-aligned reference frames. `body_names=None` includes every tracked body. | -| `motion_relative_body_orientation_error_exp` | `command_name`, `std`, `body_names=None` | Multi-body geodesic rotation error against anchor-aligned references. | -| `motion_global_body_linear_velocity_error_exp` | `command_name`, `std`, `body_names=None` | L2 world-frame linear-velocity error across the tracked bodies. | -| `motion_global_body_angular_velocity_error_exp` | `command_name`, `std`, `body_names=None` | L2 world-frame angular-velocity error across the tracked bodies. | - -!!! warning "Placeholder rewards" - `joint_acc_l2` and `feet_air_time` are stubs awaiting proper accel / - contact buffers. They compile and emit a tensor of the correct shape but - the *value* is not the locomotion-grade signal one would normally expect - from these names. Treat them as `weight=0.0` placeholders until they are - wired against per-step velocity history (`joint_acc_l2`) and a - `ContactSensor` (`feet_air_time` — `mdp.foot_air_time` already provides - the real signal as an observation term). - -```python -from genelab.managers import RewardTermCfg -from genelab import mdp - -rewards_cfg = { - "track_lin_vel": RewardTermCfg( - func=mdp.track_linear_velocity_xy_exp, - weight=1.0, - params={"command_name": "base_velocity", "std": 0.25}, - ), - "track_ang_vel": RewardTermCfg( - func=mdp.track_angular_velocity_z_exp, - weight=0.5, - params={"command_name": "base_velocity", "std": 0.25}, - ), - "upright": RewardTermCfg(func=mdp.upright_exp, weight=0.2), - "action_rate": RewardTermCfg(func=mdp.action_rate_l2, weight=-0.005), - "flat_ori": RewardTermCfg(func=mdp.flat_orientation_l2, weight=-0.5), -} -``` - -## Terminations - -All termination functions return shape `(B,)` bool. `TerminationTermCfg.time_out` -routes the value to the truncation buffer (RSL-RL's "info['time_out']") instead -of the terminated buffer. - -| Name | Params | Behavior | -|---|---|---| -| `time_out` | — | `episode_length_buf >= max_episode_length`. Always paired with `time_out=True`. | -| `bad_orientation` | `limit_angle=math.radians(70.0)` | True when the body z-axis tilts more than `limit_angle` from world up. | -| `root_height_below` | `min_height` | True when `root_pos.z < min_height`. | -| `bad_anchor_pos_z_only` | `command_name`, `threshold` | True when the robot anchor z drifts further than `threshold` from the reference clip. | -| `bad_anchor_ori` | `command_name`, `threshold` | True when the tilt error between robot anchor and reference exceeds `threshold` (gravity-z proxy). | -| `bad_motion_body_pos_z_only` | `command_name`, `threshold`, `body_names=None` | True when any selected body's vertical position deviates past `threshold`. | - -```python -from genelab.managers import TerminationTermCfg -from genelab import mdp - -terminations_cfg = { - "time_out": TerminationTermCfg(func=mdp.time_out, time_out=True), - "fell_over": TerminationTermCfg(func=mdp.bad_orientation, - params={"limit_angle": 1.0}), -} -``` - -## Events - -Event functions take an extra `env_ids` argument and return `None`. The -`EventTermCfg.mode` decides when they fire — `startup` once at construct, -`reset` on every env reset, `interval` on per-env countdown (requires -`interval_range_s`). - -| Name | Params | Behavior | -|---|---|---| -| `reset_root_state_uniform` | `pose_range` (`x` / `y` / `z` / `roll` / `pitch` / `yaw` → `(low, high)` dict), `velocity_range` (same axes) | Randomize floating-base pose and velocity within the given ranges. Pose offsets are added to `cfg.robot.init_pos`; orientation offsets layer on top of `cfg.robot.init_quat`. | -| `reset_joints_to_default` | `pos_jitter=0.0`, `vel_jitter=0.0` | Write the default joint pose to the selected envs, optionally with uniform ±jitter on position and velocity. | -| `push_by_setting_velocity` | `velocity_range` (`x` / `y` / `z` / `roll` / `pitch` / `yaw` → `(low, high)`) | Overwrite the base linear and angular velocity. Combined with `mode="interval"`, this is the canonical "random push" disturbance. | - -```python -from genelab.managers import EventTermCfg -from genelab import mdp - -events_cfg = { - "reset_root": EventTermCfg( - mode="reset", - func=mdp.reset_root_state_uniform, - params={"pose_range": {"yaw": (-3.14, 3.14)}, - "velocity_range": {"x": (-0.1, 0.1)}}, - ), - "push_robot": EventTermCfg( - mode="interval", - func=mdp.push_by_setting_velocity, - interval_range_s=(8.0, 12.0), - params={"velocity_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5)}}, - ), -} -``` - -## Curriculums - -| Name | Params | Behavior | -|---|---|---| -| `terrain_levels_vel` | `distance_threshold`, `demote_ratio=0.5` | Promote / demote each env's `TerrainImporter.terrain_levels` index by how far it walked from spawn. Envs that travelled more than `distance_threshold` move up; envs that travelled less than `distance_threshold * demote_ratio` move down. Levels then drive a fresh spawn-origin lookup, and the curriculum writes the new root pose into the sim. No-op when `env.scene.terrain is None`. Returns the per-env mean level so the manager logs `Curriculum/`. | - -```python -from genelab.managers import CurriculumTermCfg -from genelab.mdp.curriculums import terrain_levels_vel +Common observation functions include base velocity, projected gravity, relative joint position and +velocity, last action, generated commands, sensor data, contact features, terrain height scans, and +motion-tracking state. -curriculum_cfg = { - "terrain_levels": CurriculumTermCfg( - func=terrain_levels_vel, - params={"distance_threshold": 5.0, "demote_ratio": 0.5}, - ), -} -``` +Observation functions should return `(num_envs, d)` or `(num_envs,)` tensors. The observation +manager handles optional noise, scaling, clipping, and group concatenation. -## Noise +## Rewards and terminations -`NoiseCfg` is the abstract base; concrete subclasses plug into -`ObservationTermCfg.noise` and only fire when the enclosing -`ObservationGroupCfg.enable_corruption=True`. +Reward functions cover velocity tracking, action smoothness, joint acceleration, orientation, +limits, foot clearance, slip, air time, self collision, angular momentum, and motion-tracking +errors. Termination functions cover time-out, orientation, root height, and motion-tracking failure. -| Name | Fields | Behavior | -|---|---|---| -| `NoiseCfg` | — | Abstract base; subclass and implement `apply(data) -> Tensor`. | -| `Unoise` | `n_min=-1.0`, `n_max=1.0` | Uniform additive noise sampled in `[n_min, n_max]`. | -| `Gnoise` | `mean=0.0`, `std=1.0` | Gaussian additive noise `N(mean, std²)`. | +Reward functions should return `(num_envs,)`; termination functions should return `(num_envs,)` +boolean tensors. -```python -from genelab.managers import ObservationGroupCfg, ObservationTermCfg -from genelab.mdp.noise import Gnoise, Unoise -from genelab import mdp +## Events, curricula, metrics, and noise -observations_cfg = { - "policy": ObservationGroupCfg( - enable_corruption=True, - terms={ - "base_lin_vel": ObservationTermCfg(func=mdp.base_lin_vel, - noise=Unoise(n_min=-0.1, n_max=0.1)), - "joint_pos_rel": ObservationTermCfg(func=mdp.joint_pos_rel, - noise=Gnoise(std=0.01)), - }, - ), -} -``` +| Area | Examples | +|---|---| +| Events | `reset_root_state_uniform`, `reset_joints_to_default`, `push_by_setting_velocity` | +| Curricula | `terrain_levels_vel`, `commands_vel` | +| Metrics | `mean_action_acc`, `angular_momentum_mean`, `air_time_mean`, `slip_velocity_mean` | +| Noise | `Unoise`, `Gnoise` | +| Domain randomization | `mdp.dr.body`, `mdp.dr.joint`, `mdp.dr.geom` | -## See also +## Where to continue - [Managers and MDP terms](managers.md) -- [Sensors](sensors.md) +- [Task design](../best-practices/task-design.md) - [API Reference](../api/reference.md) diff --git a/docs/concepts/mdp.zh.md b/docs/concepts/mdp.zh.md index 6fc7efc4..a5c261ad 100644 --- a/docs/concepts/mdp.zh.md +++ b/docs/concepts/mdp.zh.md @@ -1,273 +1,43 @@ # MDP term 参考 -`genelab.mdp` 是一组可直接插入七个 manager 的 Python 可调用对象。所有函数遵循相同 -签名形状 —— observation / reward / termination 是 `(env, **params) -> torch.Tensor`, -event 是 `(env, env_ids, **params) -> None`,curriculum 是 -`(env, env_ids, **params) -> Tensor` —— 并由相应 `*TermCfg.func`(或 `class_type`) -字段引用。 +`genelab.mdp` 是可复用 term 库。它本身不定义任务;任务配置从这个库里选择函数和类,并接到 manager 中。 -## 可复用 building block +## Actions 与 commands -下表列出的所有名字都已在 `genelab.mdp.__init__` 中 re-export,因此 -`from genelab import mdp` 就能拿到。完整模块结构: +| 区域 | 公开组件 | +|---|---| +| Actions | `JointPositionActionCfg`、`JointPositionAction` | +| 速度命令 | `UniformVelocityCommandCfg`、`UniformVelocityCommand` | +| Motion 命令 | `MotionCommandCfg`、`MotionCommand`、`MotionLoader` | -``` -genelab.mdp.actions → JointPositionAction(Cfg) -genelab.mdp.commands → UniformVelocityCommand(Cfg)、MotionCommand(Cfg)、MotionLoader -genelab.mdp.observations → 16 个 observation 函数 -genelab.mdp.rewards → 15 个 reward 函数 / 类 -genelab.mdp.terminations → 6 个 termination 函数 -genelab.mdp.events → 3 个 event 函数 -genelab.mdp.curriculums → 1 个 curriculum 函数 -genelab.mdp.noise → NoiseCfg 基类 + Unoise + Gnoise -``` - -下面 shape 列里 `B = num_envs`、`D = action / joint / sensor 维度`、 -`N = 身体段或脚的个数`。reward 与 termination term 总是返回 `(B,)`;observation -term 返回 `(B, D)`(或 `(B,)`,会被 `ObservationManager` 自动 unsqueeze 成 `(B, 1)`)。 - -## Actions - -| 名字 | 签名(用到的 Cfg 字段) | 行为 | -|---|---|---| -| `JointPositionAction` | `JointPositionActionCfg(joint_names, scale, use_default_offset, asset_name)` | `target = default + scale * raw_action`。目标通过 `Articulation.write_joint_targets_partial` 派发,按每个 joint 所在 actuator group 声明的通道执行(implicit PD 走 position,`IdealPDActuator` / `DCMotorActuator` 走 force)。`scale=None`(默认)从 `Articulation.action_scale_tensor` 继承逐 joint 比例;`float` 或 `dict[str, float]` 可覆盖。`joint_names` 为 regex,匹配不到任何 joint 会抛错。 | - -```python -from genelab.mdp.actions.joint_position import JointPositionActionCfg - -actions_cfg = { - "panda_arm": JointPositionActionCfg( - asset_name="robot", - joint_names=(r"^joint[1-7]$",), - use_default_offset=True, - ), -} -``` - -## Commands - -| 名字 | 签名(用到的 Cfg 字段) | 行为 | -|---|---|---| -| `UniformVelocityCommand` | `UniformVelocityCommandCfg(ranges, rel_standing_envs, heading_command, heading_control_stiffness, resampling_time_range, asset_name)` | 每 env 一组 body-frame `[lin_vel_x, lin_vel_y, ang_vel_z]`,每 `resampling_time_range` 秒从 `ranges` 均匀重采样。`heading_command=True`(默认)时,`ang_vel_z` 每步被重写以把 body 朝向目标 heading 拉。`rel_standing_envs` 把一定比例的重采样命令清零,让 policy 也见到 "stand still" 目标。`command` shape `(B, 3)`。 | -| `MotionCommand` | `MotionCommandCfg(motion_file, anchor_body_name, body_names, motion_body_order, motion_joint_order, pose_range, velocity_range, sampling_mode, ...)` | 按帧驱动 env 跟随预录动作 clip。读 mjlab `csv_to_npz` 产生的 NPZ schema。`motion_body_order` / `motion_joint_order` 描述 NPZ 内部 body / joint 轴的原生顺序(通常是 mjlab MJCF 的 DFS),`MotionLoader` 据此把数据重排到运行时机器人的顺序 —— 上游遍历顺序与 Genesis 不一致时必须传入。暴露 anchor 与多 body 的 reference / current 位姿 / 速度,供下节相对位姿 reward 函数使用。`sampling_mode="start"` 始终从第 0 帧开始;`"uniform"`(默认)随机抽帧。 | -| `MotionLoader` | `MotionLoader(motion_file, body_indexes, device, joint_perm=None)` | `MotionCommand` 内部用的辅助类。把 NPZ 读入设备 tensor,按可选的 `joint_perm` 重排 joint 轴,并把 `body_pos_w / body_quat_w / body_lin_vel_w / body_ang_vel_w` 切到要跟踪的几个 body。直接使用极少 —— 通常实例化 `MotionCommandCfg` 即可。 | - -```python -from genelab.mdp.commands.velocity_command import UniformVelocityCommandCfg - -commands_cfg = { - "base_velocity": UniformVelocityCommandCfg( - asset_name="robot", - resampling_time_range=(10.0, 10.0), - ranges=UniformVelocityCommandCfg.Ranges( - lin_vel_x=(-1.0, 1.0), lin_vel_y=(-0.5, 0.5), ang_vel_z=(-1.0, 1.0), - ), - heading_command=True, - ), -} -``` +action 把策略输出转换为仿真控制。command 持有 observation 和 reward 可读取的采样目标。 ## Observations -下列所有函数都接 `env` 加注明的 kwargs,并返回 shape `(B, D)` 或 `(B,)` 的 tensor -(后者会被 manager auto-unsqueeze 成 `(B, 1)`)。 - -| 名字 | Params | Shape | 来源 | -|---|---|---|---| -| `base_lin_vel` | — | `(B, 3)` | body-frame 浮动基线速度。 | -| `base_ang_vel` | — | `(B, 3)` | body-frame 浮动基角速度。 | -| `projected_gravity` | — | `(B, 3)` | 世界重力投影到 body frame(IMU 朝向 proxy)。 | -| `joint_pos_rel` | — | `(B, num_dofs)` | 关节位置减默认 pose。 | -| `joint_vel_rel` | — | `(B, num_dofs)` | 关节速度(默认为零,等同 raw `joint_vel`)。 | -| `last_action` | — | `(B, total_action_dim)` | 上一步 manager 处理的 action tensor。 | -| `generated_commands` | `command_name` | `(B, command_dim)` | `env.command_manager.get_command(command_name)`。 | -| `sensor_data` | `sensor_name` | 视 sensor 而定 | `env.sensors[sensor_name]` 上每步缓存的 tensor。适用于 IMU、FrameTransformer、ray-cast 等输出已是 tensor 的 sensor。 | -| `foot_air_time` | `sensor_name`(必须是 `ContactSensor`) | `(B, N_feet)` | 每脚的当前 air time(接触时为零)。 | -| `foot_contact` | `sensor_name`(必须是 `ContactSensor`) | `(B, N_feet)` | 接触二值 mask(float)。 | -| `foot_contact_forces` | `sensor_name`(必须是 `ContactSensor`) | `(B, N_feet * 3)` | 每脚接触力,按 `sign(f) * log1p(|f|)` 压缩并铺平。 | -| `height_scan` | `sensor_name`(必须是 `TerrainHeightSensor`) | `(B, num_rays)` | 每条射线相对地形的高度。 | -| `motion_anchor_pos_b` | `command_name`(必须是 `MotionCommand`) | `(B, 3)` | 参考 anchor 位置在 robot anchor frame 下的表示。 | -| `motion_anchor_ori_b` | `command_name` | `(B, 6)` | 参考 6D 朝向(rotation matrix 前两列)。 | -| `robot_body_pos_b` | `command_name` | `(B, N_bodies * 3)` | 每身体段在 robot anchor frame 下的位置(critic 用的特权观测)。 | -| `robot_body_ori_b` | `command_name` | `(B, N_bodies * 6)` | 每身体段在 robot anchor frame 下的 6D 朝向。 | - -```python -from genelab.managers import ObservationGroupCfg, ObservationTermCfg -from genelab import mdp -from genelab.mdp.noise import Unoise - -observations_cfg = { - "policy": ObservationGroupCfg( - terms={ - "base_lin_vel": ObservationTermCfg(func=mdp.base_lin_vel, - noise=Unoise(n_min=-0.1, n_max=0.1)), - "joint_pos_rel": ObservationTermCfg(func=mdp.joint_pos_rel), - "last_action": ObservationTermCfg(func=mdp.last_action), - "commands": ObservationTermCfg(func=mdp.generated_commands, - params={"command_name": "base_velocity"}), - }, - enable_corruption=True, - ), -} -``` - -## Rewards - -所有 reward 函数返回 shape `(B,)`。`RewardManager` 会乘 `RewardTermCfg.weight`,并在 -`scale_rewards_by_dt=True` 时再乘 `dt`。负 weight 把"偏差"型 reward 变成惩罚。 - -| 名字 | Params | 行为 | -|---|---|---| -| `track_linear_velocity_xy_exp` | `command_name`、`std=0.5` | `exp(-||cmd_xy − vel_xy||² / std²)`。 | -| `track_angular_velocity_z_exp` | `command_name`、`std=0.5` | `exp(-(cmd_z − vel_z)² / std²)`。 | -| `action_rate_l2` | — | `Σ_d (action_d − prev_action_d)²` —— 惩罚 action 抖动。 | -| `joint_acc_l2` | — | **占位**(当前返回零;待加 accel buffer)。 | -| `flat_orientation_l2` | — | `Σ (projected_gravity_xy)²` —— 惩罚倾斜。 | -| `upright_exp` | `std=0.45` | `exp(-||projected_gravity_xy||² / std²)` —— 站直时正 reward。 | -| `variable_posture` | `command_name`、`std_standing` / `std_walking` / `std_running`(regex → float 字典)、`default_std`、`walking_threshold`、`running_threshold` | 速度自适应姿态 reward:`exp(-mean((joint_pos − default)² / std²))`,每 env 按命令模长挑 std。class 风格 term —— 构造时一次性 `__init__`,每步走 `__call__`。 | -| `joint_pos_limits` | — | 关节位置超过 ±π 部分的 L2。 | -| `feet_air_time` | `threshold=0.4` | **stub** —— 用 foot link 平均高度近似 air-time,超过 `threshold` 饱和。 | -| `motion_global_anchor_position_error_exp` | `command_name`、`std` | 世界系 anchor 位置 `exp(-||p_ref − p_robot||² / std²)`。 | -| `motion_global_anchor_orientation_error_exp` | `command_name`、`std` | 世界系 anchor 朝向几何 rotation error 套 Gaussian 核。 | -| `motion_relative_body_position_error_exp` | `command_name`、`std`、`body_names=None` | anchor-aligned 多 body 位置 L2 误差。`body_names=None` 表示所有被跟踪 body。 | -| `motion_relative_body_orientation_error_exp` | `command_name`、`std`、`body_names=None` | anchor-aligned 多 body 几何 rotation 误差。 | -| `motion_global_body_linear_velocity_error_exp` | `command_name`、`std`、`body_names=None` | 世界系多 body 线速度 L2 误差。 | -| `motion_global_body_angular_velocity_error_exp` | `command_name`、`std`、`body_names=None` | 世界系多 body 角速度 L2 误差。 | - -!!! warning "占位 reward" - `joint_acc_l2` 与 `feet_air_time` 是 stub,等正经 accel / 接触缓冲落地。它们能正常 - 返回 shape 正确的 tensor,但**值本身不是**通常意义下的 locomotion 信号。除非已经 - 把它们接到逐步速度历史(`joint_acc_l2`)与 `ContactSensor`(`feet_air_time` —— - `mdp.foot_air_time` 作为 observation term 已经能给真实信号),否则按 `weight=0.0` - 占位处理。 - -```python -from genelab.managers import RewardTermCfg -from genelab import mdp - -rewards_cfg = { - "track_lin_vel": RewardTermCfg( - func=mdp.track_linear_velocity_xy_exp, - weight=1.0, - params={"command_name": "base_velocity", "std": 0.25}, - ), - "track_ang_vel": RewardTermCfg( - func=mdp.track_angular_velocity_z_exp, - weight=0.5, - params={"command_name": "base_velocity", "std": 0.25}, - ), - "upright": RewardTermCfg(func=mdp.upright_exp, weight=0.2), - "action_rate": RewardTermCfg(func=mdp.action_rate_l2, weight=-0.005), - "flat_ori": RewardTermCfg(func=mdp.flat_orientation_l2, weight=-0.5), -} -``` - -## Terminations - -所有 termination 函数返回 shape `(B,)` bool。`TerminationTermCfg.time_out` 把返回值 -送进 truncation buffer(RSL-RL 的 `info["time_out"]`),而不是 terminated buffer。 - -| 名字 | Params | 行为 | -|---|---|---| -| `time_out` | — | `episode_length_buf >= max_episode_length`。永远配 `time_out=True`。 | -| `bad_orientation` | `limit_angle=math.radians(70.0)` | body z 轴相对世界向上方向倾斜超过 `limit_angle` 时为 True。 | -| `root_height_below` | `min_height` | `root_pos.z < min_height` 时为 True。 | -| `bad_anchor_pos_z_only` | `command_name`、`threshold` | robot anchor z 相对 reference clip 漂移超过 `threshold` 时为 True。 | -| `bad_anchor_ori` | `command_name`、`threshold` | robot anchor 与 reference 倾斜误差(重力 z 投影 proxy)超过 `threshold` 时为 True。 | -| `bad_motion_body_pos_z_only` | `command_name`、`threshold`、`body_names=None` | 任一选中 body 的垂直位置偏离参考超过 `threshold` 时为 True。 | - -```python -from genelab.managers import TerminationTermCfg -from genelab import mdp - -terminations_cfg = { - "time_out": TerminationTermCfg(func=mdp.time_out, time_out=True), - "fell_over": TerminationTermCfg(func=mdp.bad_orientation, - params={"limit_angle": 1.0}), -} -``` - -## Events - -Event 函数多接一个 `env_ids` 参数,返回 `None`。`EventTermCfg.mode` 决定触发时机 —— -`startup` 构造期一次、`reset` 每次 env reset、`interval` 每 env 倒计时(需要 -`interval_range_s`)。 - -| 名字 | Params | 行为 | -|---|---|---| -| `reset_root_state_uniform` | `pose_range`(`x` / `y` / `z` / `roll` / `pitch` / `yaw` → `(low, high)` 字典)、`velocity_range`(同样的轴) | 在给定范围内随机化浮动基位姿与速度。位姿偏移叠加在 `cfg.robot.init_pos` 上;朝向偏移叠加在 `cfg.robot.init_quat` 上。 | -| `reset_joints_to_default` | `pos_jitter=0.0`、`vel_jitter=0.0` | 把默认关节 pose 写入选中 env,可选叠加位置与速度的 uniform ±jitter。 | -| `push_by_setting_velocity` | `velocity_range`(`x` / `y` / `z` / `roll` / `pitch` / `yaw` → `(low, high)`) | 覆盖 base 线速度与角速度。配合 `mode="interval"` 就是标准的 "随机推" 扰动。 | - -```python -from genelab.managers import EventTermCfg -from genelab import mdp - -events_cfg = { - "reset_root": EventTermCfg( - mode="reset", - func=mdp.reset_root_state_uniform, - params={"pose_range": {"yaw": (-3.14, 3.14)}, - "velocity_range": {"x": (-0.1, 0.1)}}, - ), - "push_robot": EventTermCfg( - mode="interval", - func=mdp.push_by_setting_velocity, - interval_range_s=(8.0, 12.0), - params={"velocity_range": {"x": (-0.5, 0.5), "y": (-0.5, 0.5)}}, - ), -} -``` - -## Curriculums - -| 名字 | Params | 行为 | -|---|---|---| -| `terrain_levels_vel` | `distance_threshold`、`demote_ratio=0.5` | 按每 env 离 spawn 走了多远,调整 `TerrainImporter.terrain_levels` 索引。走超过 `distance_threshold` 上一档;走少于 `distance_threshold * demote_ratio` 下一档。新 level 触发 spawn origin 重查,curriculum 把新 root pose 写回 sim。`env.scene.terrain is None` 时整体 no-op。返回每 env 平均 level,manager 自动按 `Curriculum/` 写日志。 | - -```python -from genelab.managers import CurriculumTermCfg -from genelab.mdp.curriculums import terrain_levels_vel +常见 observation 函数包括 base velocity、projected gravity、relative joint position/velocity、 +last action、generated commands、sensor data、contact 特征、terrain height scan 和 motion-tracking 状态。 -curriculum_cfg = { - "terrain_levels": CurriculumTermCfg( - func=terrain_levels_vel, - params={"distance_threshold": 5.0, "demote_ratio": 0.5}, - ), -} -``` +observation 函数应返回 `(num_envs, d)` 或 `(num_envs,)` tensor。Observation manager 负责可选噪声、缩放、裁剪和 group concat。 -## Noise +## Rewards 与 terminations -`NoiseCfg` 是抽象基类;具体子类挂在 `ObservationTermCfg.noise` 上,仅当所在 -`ObservationGroupCfg.enable_corruption=True` 时生效。 +reward 函数覆盖速度跟踪、action 平滑、关节加速度、姿态、关节限制、足端 clearance、slip、air time、 +self collision、角动量和 motion-tracking 误差。termination 函数覆盖 time-out、姿态、root height 和 motion-tracking 失败。 -| 名字 | 字段 | 行为 | -|---|---|---| -| `NoiseCfg` | — | 抽象基类;继承并实现 `apply(data) -> Tensor`。 | -| `Unoise` | `n_min=-1.0`、`n_max=1.0` | uniform 噪声 `[n_min, n_max]`,加性叠加。 | -| `Gnoise` | `mean=0.0`、`std=1.0` | Gaussian 噪声 `N(mean, std²)`,加性叠加。 | +reward 函数应返回 `(num_envs,)`;termination 函数应返回 `(num_envs,)` bool tensor。 -```python -from genelab.managers import ObservationGroupCfg, ObservationTermCfg -from genelab.mdp.noise import Gnoise, Unoise -from genelab import mdp +## Events、curricula、metrics、noise -observations_cfg = { - "policy": ObservationGroupCfg( - enable_corruption=True, - terms={ - "base_lin_vel": ObservationTermCfg(func=mdp.base_lin_vel, - noise=Unoise(n_min=-0.1, n_max=0.1)), - "joint_pos_rel": ObservationTermCfg(func=mdp.joint_pos_rel, - noise=Gnoise(std=0.01)), - }, - ), -} -``` +| 区域 | 示例 | +|---|---| +| Events | `reset_root_state_uniform`、`reset_joints_to_default`、`push_by_setting_velocity` | +| Curricula | `terrain_levels_vel`、`commands_vel` | +| Metrics | `mean_action_acc`、`angular_momentum_mean`、`air_time_mean`、`slip_velocity_mean` | +| Noise | `Unoise`、`Gnoise` | +| Domain randomization | `mdp.dr.body`、`mdp.dr.joint`、`mdp.dr.geom` | -## See also +## 继续阅读 -- [Managers and MDP terms](managers.md) -- [Sensors](sensors.md) -- [API Reference](../api/reference.md) +- [Manager 与 MDP term](managers.md) +- [设计任务](../best-practices/task-design.md) +- [API 参考](../api/reference.md) diff --git a/docs/concepts/recording.en.md b/docs/concepts/recording.en.md index a7d47eee..1798767d 100644 --- a/docs/concepts/recording.en.md +++ b/docs/concepts/recording.en.md @@ -1,185 +1,41 @@ -# Recording and live plotting +# Recording and Live Plotting -`genelab.recording` packages Genesis's recorder + live-plotter stack behind a -declarative `RecordingCfg`. A scene can declare one or more recordings, each pairing a -data source (sensor, articulation field, or custom callable) with one or more output -sinks (PyQt/Matplotlib plot, NPZ/CSV file, MP4/AVI video). The Genesis recorder pipeline -does the actual sampling on its own threads — Genelab just describes what to record and -wires the env lifecycle. - -## Quick start - -```python -from genelab.configs import InteractiveSceneCfg, SimulationCfg -from genelab.recording import NPZFileCfg, PyQtPlotCfg, RecordingCfg -from genelab.sensor import IMUSensorCfg - -scene = InteractiveSceneCfg( - sensors=(IMUSensorCfg(name="cart_imu", link_name="cart"),), - recordings=( - RecordingCfg( - name="cart_acc", - source="cart_imu", - field="lin_acc_b", - outputs=( - PyQtPlotCfg(title="cart linear acc", labels=("ax", "ay", "az")), - NPZFileCfg(filename="logs/cart_acc.npz"), - ), - ), - ), -) -``` - -Running `env.step(...)` then samples `cart_imu.data.lin_acc_b` once per control step, -streams it to a live PyQt window, and accumulates an `.npz` file written when the env -is closed. No manual data plumbing in your runner. +Recording turns selected runtime signals into live plots, files, or video without embedding output +code in the environment loop. ## Mental model -* **One `RecordingCfg` = one source × N sinks.** Outputs share the same data callable. -* **Registration is scene-build-time.** Genesis's `add_recorder` is `@assert_unbuilt`, - so recordings land in `InteractiveSceneCfg` (next to `sensors`), not on the env-level - `*_cfg` dicts. A small `RecorderBridge` carries the not-yet-bound env reference - through to the callables. -* **Sampling happens inside `gs_scene.step()`.** Recorders run on Genesis threads — - PyQt plots update without blocking the env loop. - -## Sources - -A `RecordingCfg.source` is one of: - -* **Sensor name (string).** Combined with `field=...` to pick a tensor from the sensor's - `data` payload (`"lin_acc_b"`, `"orientation"`, `"rgb"`, etc.). If `field` is omitted, - `sensor.data` must already be a plain tensor — dataclass payloads raise a clear error. -* **Callable.** Takes either zero arguments or a single `env` argument; arity is - detected once at registration. Useful for raw articulation state or computed signals - that don't have a sensor wrapper. - -```python -# sensor + dotted field -RecordingCfg(name="imu_ori", source="imu", field="orientation", outputs=(...,)) - -# nullary callable -RecordingCfg(name="wall_clock", source=time.monotonic, outputs=(...,)) - -# env-aware callable -RecordingCfg( - name="q0", - source=lambda env: env.robot_state.joint_pos[:, 0], - outputs=(...,), -) -``` - -`RecordingCfg.env_idx` (default 0) squeezes a multi-env leading dimension to a single -env before each sample reaches the sinks; pass `env_idx=None` to keep the full batch -(useful when dumping all envs to NPZ). - -## Output reference - -| Cfg | Backend | When to use | -|-----|---------|-------------| -| `PyQtPlotCfg` | `pyqtgraph` (threaded) | Default for live plots — works on every platform Genesis supports. | -| `MPLPlotCfg` | `matplotlib` | Fallback when PyQt is unavailable; runs on the main thread on macOS (may stutter). | -| `NPZFileCfg` | `numpy.savez_compressed` | Buffered; one file per recording, flushed on `env.close()` (or per-episode with `save_on_reset=True`). | -| `CSVFileCfg` | `csv.writer` | Row-streamed; pass `header=("a","b",...)` for column names, `save_every_write=True` to flush each row. | -| `VideoFileCfg` | `cv2.VideoWriter` | MP4/AVI from a `CameraSensor`; only pairs with camera sources. | - -All five cfgs accept the common `hz`, `buffer_size`, and `buffer_full_wait_time` knobs -from `RecorderOptions`. Setting `hz` explicitly always wins over the Genelab defaults. - -## Sampling cadence and the sensor cache - -`Sensor.data` is cached per control step. If a recorder sampled every physics tick it -would record `decimation` identical rows per control step. Genelab fixes this for you: - -* **Sensor-name sources** default to `hz = 1 / (sim.dt * decimation)` (the control rate). -* **Callable sources** default to `hz = None` (every physics tick) — the assumption is - that the callable reads raw articulation state that genuinely changes each tick. +`RecordingCfg` describes one data source and one or more output sinks. The scene converts these +configs into Genesis recorders during build, then binds them to the live env after sensors exist. -Override either by setting `hz` on the output cfg directly: - -```python -NPZFileCfg(filename="raw.npz", hz=200.0) # explicit 200 Hz regardless of sim.dt -``` - -## Episode boundaries and `save_on_reset` - -When at least one output has `save_on_reset=True`, the env's auto-reset path calls -`gs_scene._recorder_manager.reset()` to flush and rotate file outputs (NPZ counter, -new CSV header row). The very first reset inside `ManagerBasedRlEnv.__init__` is -skipped so file counters start at 0, matching user expectations -(`pole_0.npz`, `pole_1.npz`, …). The final partial buffer is flushed by -`env.close()`. - -Plotters are unaffected by reset (history is rolling). - -## Multi-env - -Plotters always read a single env. Use `env_idx` to pick which one — the default 0 is -almost always what you want when scaling envs for RL while debugging. - -File writers can record all envs at once: set `env_idx=None` and they store the full -`(num_envs, ...)` tensor. Plot outputs combined with `env_idx=None` will fail at the -first sample — split the recording. - -## Cameras and video - -A `CameraSensor` source paired with a single `VideoFileCfg` writes a one-env video: - -```python -RecordingCfg( - name="wrist_video", - source="wrist_cam", - outputs=(VideoFileCfg(filename="logs/wrist.mp4", env_idx=0, fps=30),), -) -``` - -Caveats: - -* The camera must have `render_rgb=True` — depth-only cameras are rejected at runtime. -* `BatchRenderer` is Linux x86-64 + CUDA only. On macOS the default Rasterizer still - produces a single-env `(1, H, W, 3)` tensor that `env_idx=0` slices correctly. -* Pairing a plotter with a camera source is rejected at registration — plotters can't - consume H×W×3 frames. - -## Custom callables and arbitrary signals - -Any quantity Genelab exposes on the env can become a recording. Examples: - -```python -# joint torque (post-actuator) -RecordingCfg( - name="joint_effort", - source=lambda env: env.robot_state.dof_force[:, env.joint_names.index("joint1")], - outputs=(NPZFileCfg(filename="logs/effort.npz"),), -) - -# reward term snapshot -RecordingCfg( - name="track_reward", - source=lambda env: env.reward_manager._term_sums["track_lin_vel"], - outputs=(PyQtPlotCfg(title="track reward"),), -) +```text +RecordingCfg(source, field, env_idx) +└── PyQtPlotCfg / MPLPlotCfg / NPZFileCfg / CSVFileCfg / VideoFileCfg ``` -The callable runs on every recorder tick (every physics step by default). Keep it -cheap — heavy compute belongs in a Genelab sensor. +## Sources -## Optional dependencies +| Source | Meaning | +|---|---| +| Sensor name | Read `env.sensors[name].data`, optionally walking `field`. | +| Callable with no args | Called directly. | +| Callable with `env` arg | Called with the live `ManagerBasedRlEnv`. | -Live plot backends live behind a `recording` extra on Genelab itself: +`env_idx` squeezes a batched tensor for per-env plotting. Use `env_idx=None` when the source already +returns a scalar or when a file should capture the full batch. -```bash -pip install "genelab[recording]" # pulls pyqtgraph + PyQt5 + matplotlib -``` +## Outputs -The showcase package depends on `genelab[recording]` transitively, so -`uv pip install -e examples/genelab_showcase` already pulls them in — no manual step. -File-only recordings (NPZ / CSV / VideoFile) need no extra dependencies beyond -Genelab's existing stack (numpy + PyAV, both Genesis transitive deps). +| Output | Use case | +|---|---| +| `PyQtPlotCfg` | Live PyQtGraph plots. | +| `MPLPlotCfg` | Live Matplotlib plots. | +| `NPZFileCfg` | Compressed array dumps at cleanup/reset. | +| `CSVFileCfg` | Row-oriented streaming output. | +| `VideoFileCfg` | Camera frames to `.mp4`. | -## See also +## Where to continue - [Sensors](sensors.md) -- [Scene and entities](scene.md) -- The `Showcase-Recording` task in [Examples → Showcase](../examples/showcase.md). +- [Debug Common Failures](../best-practices/debugging.md) +- [API Reference](../api/reference.md) diff --git a/docs/concepts/recording.zh.md b/docs/concepts/recording.zh.md index c3fc4d7f..1c4ae0c4 100644 --- a/docs/concepts/recording.zh.md +++ b/docs/concepts/recording.zh.md @@ -1,176 +1,38 @@ # 数据录制与实时绘图 -`genelab.recording` 将 Genesis 的录制器 + 实时绘图栈封装为声明式 `RecordingCfg`。 -一个场景可以声明多个录制项,每项把一个数据源(传感器、articulation 字段、或自定义 -回调)连接到一个或多个输出(PyQt/Matplotlib 实时图、NPZ/CSV 文件、MP4/AVI 视频)。 -真正的采样由 Genesis 的录制器线程完成 —— Genelab 只负责描述要录什么、把 env 的 -生命周期接好。 - -## 快速开始 - -```python -from genelab.configs import InteractiveSceneCfg, SimulationCfg -from genelab.recording import NPZFileCfg, PyQtPlotCfg, RecordingCfg -from genelab.sensor import IMUSensorCfg - -scene = InteractiveSceneCfg( - sensors=(IMUSensorCfg(name="cart_imu", link_name="cart"),), - recordings=( - RecordingCfg( - name="cart_acc", - source="cart_imu", - field="lin_acc_b", - outputs=( - PyQtPlotCfg(title="cart linear acc", labels=("ax", "ay", "az")), - NPZFileCfg(filename="logs/cart_acc.npz"), - ), - ), - ), -) -``` - -每次 `env.step(...)` 就会按控制步采样 `cart_imu.data.lin_acc_b`,实时推送到 PyQt -窗口,并累积一个 `.npz` 文件在 `env.close()` 时写出。runner 里不需要手写任何数据 -搬运代码。 +Recording 把选定运行时信号变成实时曲线、文件或视频,而不需要把输出代码嵌入环境循环。 ## 心智模型 -* **一个 `RecordingCfg` = 一个数据源 × N 个输出**。所有输出共享同一个数据 callable。 -* **注册发生在 scene-build 时刻**。Genesis 的 `add_recorder` 标了 `@assert_unbuilt`, - 所以录制配置放在 `InteractiveSceneCfg`(紧挨 `sensors`),不在 env 层的 - `*_cfg` 字典里。小小的 `RecorderBridge` 把还未构造的 env 引用透传给回调。 -* **采样在 `gs_scene.step()` 内部触发**。录制器跑在 Genesis 的线程上 —— PyQt 实时 - 图不会阻塞 env 主循环。 - -## 数据源 - -`RecordingCfg.source` 可以是: - -* **传感器名(字符串)**。配合 `field=...` 从该传感器的 `data` 里取一个张量 - (`"lin_acc_b"`、`"orientation"`、`"rgb"` 等)。如果省略 `field`,要求 - `sensor.data` 本身就是普通张量 —— 数据类负载会抛出清晰的错误。 -* **可调用对象**。零参或单 `env` 参数;arity 在注册时检测一次。适合裸的 - articulation 状态或没有专门 sensor 包装的派生信号。 - -```python -# 传感器 + dotted field -RecordingCfg(name="imu_ori", source="imu", field="orientation", outputs=(...,)) - -# 零参 callable -RecordingCfg(name="wall_clock", source=time.monotonic, outputs=(...,)) - -# 带 env 的 callable -RecordingCfg( - name="q0", - source=lambda env: env.robot_state.joint_pos[:, 0], - outputs=(...,), -) -``` - -`RecordingCfg.env_idx`(默认 0)会把多 env 的首维度切片到单 env 再交给下游 sink; -传 `env_idx=None` 可保留完整批次(适合把所有 env 一次性 dump 到 NPZ)。 - -## 输出参考 - -| Cfg | 后端 | 适用场景 | -|-----|------|---------| -| `PyQtPlotCfg` | `pyqtgraph`(线程化) | 实时绘图首选 —— Genesis 支持的所有平台都能用 | -| `MPLPlotCfg` | `matplotlib` | PyQt 不可用时的 fallback;macOS 下走主线程,可能卡顿 | -| `NPZFileCfg` | `numpy.savez_compressed` | 缓存到内存,`env.close()` 时一次写出(或开启 `save_on_reset=True` 按 episode 切文件)| -| `CSVFileCfg` | `csv.writer` | 行流式;`header=("a","b",...)` 设列名,`save_every_write=True` 每行 flush | -| `VideoFileCfg` | `cv2.VideoWriter` | 来自 `CameraSensor` 的 MP4/AVI;只能配相机源 | - -所有五个 cfg 都接受 `RecorderOptions` 的公共字段:`hz`、`buffer_size`、 -`buffer_full_wait_time`。显式设置 `hz` 总会覆盖 Genelab 的默认策略。 - -## 采样频率与传感器缓存 - -`Sensor.data` 在每个控制步缓存。如果录制器按物理 tick 采样,会得到 `decimation` 个 -重复行。Genelab 自动处理这一点: - -* **传感器源**默认 `hz = 1 / (sim.dt * decimation)`(即控制率)。 -* **Callable 源**默认 `hz = None`(每个物理 tick 都采)—— 假设 callable 读裸状态, - 每个 tick 都会变。 +`RecordingCfg` 描述一个数据源和一个或多个输出 sink。scene 在 build 时把这些配置转换成 Genesis recorder,并在传感器存在后绑定到 live env。 -显式重写时直接在输出 cfg 上设 `hz`: - -```python -NPZFileCfg(filename="raw.npz", hz=200.0) # 强制 200 Hz,无视 sim.dt -``` - -## Episode 边界与 `save_on_reset` - -当至少有一个输出开了 `save_on_reset=True`,env 的 auto-reset 会调用 -`gs_scene._recorder_manager.reset()`,flush 并轮换文件输出(NPZ 计数器自增、CSV -重写表头)。`ManagerBasedRlEnv.__init__` 里的首次 reset 会被跳过,所以文件计数从 0 -开始,符合直觉(`pole_0.npz`、`pole_1.npz`、…)。最后没写完的缓存由 `env.close()` -flush 出去。 - -绘图器不受 reset 影响(历史是滚动窗口)。 - -## 多 env - -绘图器永远只读一个 env。用 `env_idx` 选哪个 —— 默认 0,几乎总是 RL 调试时要的。 - -文件写出器可以一次记录所有 env:设 `env_idx=None`,就会存完整 `(num_envs, ...)` -张量。把绘图器和 `env_idx=None` 配在一起会在首次采样时报错 —— 拆成两个 recording。 - -## 相机与视频 - -一个 `CameraSensor` 源配单个 `VideoFileCfg` 就能写出单 env 的视频: - -```python -RecordingCfg( - name="wrist_video", - source="wrist_cam", - outputs=(VideoFileCfg(filename="logs/wrist.mp4", env_idx=0, fps=30),), -) -``` - -注意: - -* 相机必须开 `render_rgb=True` —— 仅深度相机会在运行时报错。 -* `BatchRenderer` 只支持 Linux x86-64 + CUDA。macOS 下默认 Rasterizer 仍会输出 - 单 env 的 `(1, H, W, 3)` 张量,`env_idx=0` 正确切片。 -* 绘图器配相机源会在注册阶段被拒绝 —— 绘图器吃不下 H×W×3 帧。 - -## 自定义 callable 和任意信号 - -任何 env 上能取到的量都能录。例子: - -```python -# 关节扭矩(actuator 之后) -RecordingCfg( - name="joint_effort", - source=lambda env: env.robot_state.dof_force[:, env.joint_names.index("joint1")], - outputs=(NPZFileCfg(filename="logs/effort.npz"),), -) - -# 奖励项快照 -RecordingCfg( - name="track_reward", - source=lambda env: env.reward_manager._term_sums["track_lin_vel"], - outputs=(PyQtPlotCfg(title="track reward"),), -) +```text +RecordingCfg(source, field, env_idx) +└── PyQtPlotCfg / MPLPlotCfg / NPZFileCfg / CSVFileCfg / VideoFileCfg ``` -Callable 在每次录制 tick 都会跑(默认每个物理步)。保持轻量 —— 重计算应该放进 -Genelab sensor。 +## 数据源 -## 可选依赖 +| Source | 含义 | +|---|---| +| 传感器名 | 读取 `env.sensors[name].data`,可选沿 `field` 取字段。 | +| 无参 callable | 直接调用。 | +| 带 `env` 参数的 callable | 用 live `ManagerBasedRlEnv` 调用。 | -实时绘图的后端都在 Genelab 的 `recording` extra 里: +`env_idx` 会压缩 batched tensor 的 env 维,用于单 env 绘图。源已经返回标量,或文件需要捕获完整 batch 时,使用 `env_idx=None`。 -```bash -pip install "genelab[recording]" # 拉 pyqtgraph + PyQt5 + matplotlib -``` +## 输出 -showcase 包通过 `genelab[recording]` 间接依赖,`uv pip install -e -examples/genelab_showcase` 已经会自动带上 —— 不需要手装。只写文件的录制 -(NPZ / CSV / VideoFile)不需要额外依赖(numpy + PyAV 都是 Genesis 自带)。 +| Output | 用途 | +|---|---| +| `PyQtPlotCfg` | 实时 PyQtGraph 曲线。 | +| `MPLPlotCfg` | 实时 Matplotlib 曲线。 | +| `NPZFileCfg` | cleanup/reset 时写压缩数组。 | +| `CSVFileCfg` | 行式流式输出。 | +| `VideoFileCfg` | 相机帧写 `.mp4`。 | -## 另见 +## 继续阅读 - [传感器](sensors.md) -- [Scene 与实体](scene.md) -- Examples → Showcase 中的 `Showcase-Recording` 任务。 +- [调试常见问题](../best-practices/debugging.md) +- [API 参考](../api/reference.md) diff --git a/docs/concepts/registry.en.md b/docs/concepts/registry.en.md index 4e057c80..1745fe1c 100644 --- a/docs/concepts/registry.en.md +++ b/docs/concepts/registry.en.md @@ -1,50 +1,46 @@ # Registry -GeneLab provides a generic `Registry[T]` plus three module-level singletons exported from -`genelab.lab`: +GeneLab uses small registries to keep the framework separate from downstream robot projects. +The core package does not need to know every robot, environment, or task at import time; extensions +register named factories when the CLI or user code asks for them. -| Singleton | Holds | -|-----------|-------| -| `ROBOTS` | Robot factories. | -| `ENVS` | Environment factories. | -| `TASKS` | Task factories (each task pairs an environment with optional runner / agent config). | +## Why registries exist -## Entry shape +Robotics projects often mix three concerns: asset definitions, environment construction, and +experiment execution. GeneLab separates them: -A registry entry is a 4-tuple: **`(name, description, factory, cfg_type)`**. The `factory` is a -callable invoked lazily on `get(name)`, so importing a registration site does not eagerly build -heavy objects. +| Registry | Boundary | +|---|---| +| `ROBOTS` | Named robot/asset config factories. | +| `ENVS` | Named environment factories. | +| `TASKS` | Named task factories that carry `TaskCfg` and runtime methods. | -## API surface +This gives the CLI a stable discovery model without forcing a central list of all downstream +projects into `genelab` itself. -```python -from genelab.lab import ROBOTS, ENVS, TASKS, TaskCfg +## Lazy factories -def make_my_robot(): - ... +Registry entries store factories rather than constructed objects. Listing names and descriptions is +cheap; expensive work such as importing Genesis, downloading assets, or building a scene happens +only when the factory is called. -ROBOTS.register( - name="my_robot", - description="A demo robot.", - factory=make_my_robot, - cfg_type=None, -) +This is why a command like `genelab list tasks` can be fast while `genelab play TASK` is allowed to +initialize the simulator. -# Later: -robot = ROBOTS.get("my_robot") -``` +## Extension loading -The `name` is the canonical lookup key; the `description` is what `genelab list robots` -displays. +Extensions can register in three ways: -## Idempotent extension loading +| Mechanism | Use case | +|---|---| +| `genelab.extensions` entry point | Daily use after installing a package. | +| `--import MODULE` | Local experiments, notebooks, and packages not installed as entry points. | +| `load_extension_module()` | Programmatic embedding. | -The registry module tracks `_loaded_extension_modules` and `_loaded_entrypoints` sets so that -repeated calls to load the same extension are no-ops. This matters when entry-point -auto-discovery (default) combines with explicit `--import` flags — both pathways end up calling -`load_extension_module`, but the second call is short-circuited. +Registration should be idempotent when a module may be loaded repeatedly in one process. -## See also +## Where to continue -- [Extensions](extensions.md) +- [Build an Extension Project](../best-practices/extension-projects.md) +- [Discovery: list and info](../cli/list-info.md) - [API Reference](../api/reference.md) diff --git a/docs/concepts/registry.zh.md b/docs/concepts/registry.zh.md index bb4ef964..fa647f1e 100644 --- a/docs/concepts/registry.zh.md +++ b/docs/concepts/registry.zh.md @@ -1,46 +1,41 @@ # 注册表 -GeneLab 提供泛型 `Registry[T]` 与三个由 `genelab.lab` 导出的模块级单例: +GeneLab 使用小型注册表把框架和下游机器人项目分开。核心包不需要在导入时知道所有机器人、 +环境和任务;扩展在 CLI 或用户代码需要时,把命名 factory 注册进来。 -| 单例 | 持有内容 | -|------|---------| -| `ROBOTS` | 机器人工厂。 | -| `ENVS` | 环境工厂。 | -| `TASKS` | 任务工厂(每个任务把环境与可选 runner / agent 配置打包)。 | +## 为什么需要注册表 -## 条目结构 +机器人项目常把三件事混在一起:资产定义、环境构造、实验执行。GeneLab 把它们拆开: -注册条目是一个四元组:**`(name, description, factory, cfg_type)`**。`factory` 是一个可调用, -只在 `get(name)` 时才会被调用,因此导入注册位点不会立即构造重型对象。 +| 注册表 | 边界 | +|---|---| +| `ROBOTS` | 命名机器人/资产配置 factory。 | +| `ENVS` | 命名环境 factory。 | +| `TASKS` | 命名任务 factory,持有 `TaskCfg` 与运行方法。 | -## 接口速览 +这样 CLI 可以有稳定的发现模型,而不用把所有下游项目硬编码进 `genelab`。 -```python -from genelab.lab import ROBOTS, ENVS, TASKS, TaskCfg +## 惰性 factory -def make_my_robot(): - ... +注册表条目保存 factory,而不是已经构造好的对象。列名字和描述很轻量;导入 Genesis、下载资产、 +构建 scene 这类昂贵工作只在 factory 被调用时发生。 -ROBOTS.register( - name="my_robot", - description="A demo robot.", - factory=make_my_robot, - cfg_type=None, -) +因此 `genelab list tasks` 可以很快,而 `genelab play TASK` 可以启动仿真器。 -# 之后: -robot = ROBOTS.get("my_robot") -``` +## 扩展加载 -`name` 是规范查找键;`description` 出现在 `genelab list robots` 输出里。 +扩展有三种注册方式: -## 幂等扩展加载 +| 机制 | 用途 | +|---|---| +| `genelab.extensions` entry point | 包安装后的日常使用。 | +| `--import MODULE` | 本地实验、notebook、未安装 entry point 的包。 | +| `load_extension_module()` | 程序内嵌入。 | -注册模块维护 `_loaded_extension_modules` 与 `_loaded_entrypoints` 两个集合,重复加载同一 -扩展会被短路。entry-point 自动发现(默认)与显式 `--import` 同时启用时,这一点尤其 -重要 —— 两条路径最终都会调用 `load_extension_module`,但第二次调用直接返回。 +当模块可能在同一进程中重复加载时,注册逻辑应保持幂等。 -## See also +## 继续阅读 -- [扩展加载](extensions.md) +- [构建扩展项目](../best-practices/extension-projects.md) +- [发现:list 与 info](../cli/list-info.md) - [API 参考](../api/reference.md) diff --git a/docs/concepts/rl-runner.en.md b/docs/concepts/rl-runner.en.md index 8ce8b555..43a5db6a 100644 --- a/docs/concepts/rl-runner.en.md +++ b/docs/concepts/rl-runner.en.md @@ -1,228 +1,42 @@ -# RL runner +# RL Runner -`genelab.rl` is the thin RSL-RL integration that turns a registered task into -a runnable PPO training or replay session. The CLI subcommands `play` and -`train` dispatch into the two entry points covered below; library callers can -also reach them directly. +`genelab.rl` connects registered tasks to RSL-RL. It is deliberately thin: GeneLab owns task +resolution, config mutation, VecEnv adaptation, logs, profiling hooks, and distributed launch +helpers; RSL-RL owns the learning algorithm. -## Module surface - -| Name | Purpose | -|---|---| -| `train_task(task_id, agent_cfg, **kwargs) -> Path` | Build the task's env, wrap it in `RslRlVecEnvWrapper`, and drive RSL-RL's `OnPolicyRunner.learn`. Returns the resolved log directory. | -| `play_task(task_id, **kwargs) -> None` | Build the task's env (using `play_env` when set), then loop `policy(obs) → env.step` until the viewer closes or `max_steps` is reached. Supports `"zero"` / `"random"` / `"trained"` policies. | -| `AgentKind` | `Literal["zero", "random", "trained"]`. | -| `RslRlOnPolicyRunnerCfg` | Top-level PPO config (extends `RslRlBaseRunnerCfg`); composes `RslRlModelCfg` for actor / critic and `RslRlPpoAlgorithmCfg` for the optimiser. | -| `RslRlVecEnvWrapper` | Adapter from `ManagerBasedRlEnv` to RSL-RL's `VecEnv` protocol. Dynamic subclass of `rsl_rl.env.VecEnv` when RSL-RL is installed. | -| `VecEnvBase` | Minimal stand-alone VecEnv ABC for non-RSL-RL callers. | -| `maybe_profile(...)` | Context manager wrapping `torch.profiler`. No-op when disabled or off rank 0. | -| `profiler_enabled()` | `True` when `GENELAB_PROFILE=1`. | - -`genelab.cli` wires `play` / `train` into `play_task` / `train_task` via -`_dispatch_play` / `_dispatch_train` in `src/genelab/cli/__init__.py:384,417`. - -## train_task - -`train_task` resolves the env cfg off the registered task, applies CLI -overrides, wraps the env, runs PPO, and returns the log directory. - -| Argument | Default | Effect | -|---|---|---| -| `task_id: str` | — | Required. Looked up in the `TASKS` registry. | -| `agent_cfg: RslRlOnPolicyRunnerCfg` | — | Required. The CLI pulls this off the task's `cfg.agent`; library callers pass it directly. | -| `num_envs: int \| None` | `None` | Overrides `env_cfg.simulation.num_envs`. | -| `max_iterations: int \| None` | `None` | Overrides `agent_cfg.max_iterations`. | -| `seed: int \| None` | `None` | Sets both `env_cfg.seed` and `agent_cfg.seed`. | -| `log_root: Path \| None` | `Path("logs/rsl_rl")` | Parent under which `/` is created. | -| `log_dir: Path \| None` | `None` | Pre-resolved final directory. Takes precedence over `log_root`. The torchrun relaunch path uses this so every rank lands in the same folder. | -| `resume_from: Path \| None` | `None` | Existing checkpoint to load before `runner.learn`. | -| `prof`, `prof_out`, `prof_wait`, `prof_warmup`, `prof_active`, `prof_repeat`, `prof_record_shapes`, `prof_with_stack` | `None` | Forwarded to `maybe_profile`. Each kwarg overrides the matching `GENELAB_PROFILE_*` env var; see "Profiling integration" below. | - -Side effects on rank 0: - -- Creates `/params/env.json` and `/params/agent.json` with a - recursive JSON dump of both cfgs. -- RSL-RL writes `/model_.pt` every `agent_cfg.save_interval` - iterations. -- TensorBoard event files land under `/` directly. - -When profiling is on, `train_task` hooks `RslRlVecEnvWrapper.step` so the -profiler schedule advances once per env step. To express the schedule in PPO -iterations instead, multiply `wait` / `warmup` / `active` by -`agent_cfg.num_steps_per_env`. - -## play_task - -`play_task` builds the env (preferring `task_cfg.play_env` when set), picks a -policy according to `agent`, then loops until the viewer closes. - -| Argument | Default | Effect | -|---|---|---| -| `task_id: str` | — | Required. | -| `checkpoint: Path \| None` | `None` | Required when `agent="trained"`. | -| `num_envs: int \| None` | `None` | Overrides `env_cfg.simulation.num_envs`. | -| `agent: "zero" \| "random" \| "trained" \| None` | `None` | Defaults to `"trained"` if `checkpoint` is given, else `"zero"`. | -| `agent_cfg: RslRlOnPolicyRunnerCfg \| None` | `None` | Required only for `agent="trained"`; falls back to `task.cfg.agent`. | -| `deterministic: bool` | `True` | Forwarded to RSL-RL inference helpers. | -| `max_steps: int \| None` | `None` | Cap the rollout. `None` runs until the viewer closes. | -| `prof*` | `None` | Same semantics as `train_task`. | - -The play loop polls `env.viewer_closed` every step, so a closed Genesis -viewer terminates the rollout cleanly without raising. `env.close()` runs in -a `finally` block. - -## RslRlOnPolicyRunnerCfg - -The top-level dataclass an extension registers as `TaskCfg.agent`. Train -relies on `class_name="OnPolicyRunner"`; an extension config with any other -runner class is rejected at dispatch. +## Training flow +```text +TASKS.get(task_id) +└── TaskCfg.env + TaskCfg.agent + └── ManagerBasedRlEnv + └── RslRlVecEnvWrapper + └── rsl_rl OnPolicyRunner.learn() ``` -RslRlOnPolicyRunnerCfg -├── (inherited from RslRlBaseRunnerCfg) -│ seed=42, num_steps_per_env=24, max_iterations=300 -│ obs_groups={"actor": ("policy",), "critic": ("critic",)} -│ save_interval=50, experiment_name="exp1", run_name="" -│ logger="tensorboard" | "wandb" -│ wandb_project="genelab", wandb_tags=() -│ resume=False, load_run=".*", load_checkpoint="model_.*.pt" -│ clip_actions: float | None, upload_model=False -├── class_name="OnPolicyRunner" -├── actor: RslRlModelCfg -├── critic: RslRlModelCfg -└── algorithm: RslRlPpoAlgorithmCfg -``` - -`RslRlModelCfg` knobs (per actor / critic): `hidden_dims=(128, 128, 128)`, -`activation="elu"`, `obs_normalization=False`, optional `cnn_cfg` / -`distribution_cfg` / `rnn_type` / `rnn_hidden_dim` / `rnn_num_layers`, -`class_name="MLPModel"`. Only `class_name`, `hidden_dims`, `activation`, -`obs_normalization`, and `distribution_cfg` reach the MLP constructor — other -fields are pruned by `_prune_model_cfg`. - -`RslRlPpoAlgorithmCfg` knobs: `num_learning_epochs=5`, `num_mini_batches=4`, -`learning_rate=1e-3`, `schedule="adaptive"|"fixed"`, `gamma=0.99`, `lam=0.95`, -`entropy_coef=0.005`, `desired_kl=0.01`, `max_grad_norm=1.0`, -`value_loss_coef=1.0`, `use_clipped_value_loss=True`, `clip_param=0.2`, -`optimizer="adam"|"adamw"|"sgd"|"rmsprop"`, `class_name="PPO"`. - -## AgentKind -`AgentKind = Literal["zero", "random", "trained"]`. `play_task` selects the -policy callable from `kind`: +The main process writes `params/env.json`, `params/agent.json`, TensorBoard events, profiler traces, +and checkpoints. -| Kind | Policy | Use case | -|---|---|---| -| `"zero"` | Returns `torch.zeros((num_envs, num_actions))`. | Visual smoke before any training; passive-pose viewer rollouts. | -| `"random"` | Returns `2.0 * rand(...) - 1.0` (uniform `[-1, 1]`). | Sanity-check action bounds and reward shaping. | -| `"trained"` | Loads the checkpoint, instantiates `OnPolicyRunner`, and calls `runner.get_inference_policy()`. | Inspect a trained policy. Requires `checkpoint`. | +## Playback flow -## VecEnv wrapper +`play_task` prefers `TaskCfg.play_env` when present. It selects a policy source: -`RslRlVecEnvWrapper` adapts `ManagerBasedRlEnv` to the interface RSL-RL's -`OnPolicyRunner` expects: - -| Attribute / method | Source | +| Agent | Source | |---|---| -| `num_envs`, `device`, `max_episode_length` | `env.num_envs`, `env.device`, `env.max_episode_length`. | -| `num_actions` | `env.action_manager.total_action_dim`. | -| `num_obs`, `num_privileged_obs` | Last-axis dim of the `"policy"` / `"critic"` observation group, pre-warmed via one `observation_manager.compute()` call at construct. | -| `reset()` | `env.reset()`. Observations are wrapped in a `TensorDict` when `tensordict` is installed; otherwise returned as a plain `dict`. | -| `step(actions)` | Clips actions to `clip_actions` when set, calls `env.step`, packs `(obs, reward, dones, extras)` with `dones = terminated \| truncated` (long) and `extras["time_outs"] = truncated`. | -| `episode_length_buf`, `cfg`, `unwrapped` | Pass-throughs to the underlying env. | +| `zero` | Returns zero actions. | +| `random` | Uniform random actions. | +| `trained` | Loads a checkpoint and calls RSL-RL inference policy. | -The wrapper dynamically subclasses `rsl_rl.env.VecEnv` at import time via -`_attach_rsl_rl_base()`. When RSL-RL is missing (e.g. unit tests on a -CPU-only host) it falls back to a plain object with the same attributes. +Playback exits cleanly when the Genesis viewer closes or when `max_steps` is reached. ## Distributed training -The CLI surfaces `--gpus N` on `train`. The flag is rewritten by -`_relaunch_under_torchrun` in `cli/__init__.py:457`: - -1. Pre-resolves the log directory under `//` - on the parent process so every rank picks up the same path. -2. Strips `--gpus N` from `sys.argv` to avoid an infinite relaunch loop. -3. Appends `--log-dir ` when the original argv did not already - carry one. -4. `execvp`s `python -m torch.distributed.run --standalone --nproc_per_node=N - -m genelab.cli `. - -```bash -uv run genelab train Genelab-Velocity-Flat-Unitree-G1-v0 --gpus 4 --num-envs 4096 -``` - -Inside each rank, `genelab.rl.distributed` reads `WORLD_SIZE` / `LOCAL_RANK` / -`RANK` from the environment: - -| Helper | Returns | -|---|---| -| `world_size()` | `int(os.environ["WORLD_SIZE"])` or `1`. | -| `local_rank()` | `int(os.environ["LOCAL_RANK"])` or `0`. | -| `global_rank()` | `int(os.environ["RANK"])` or `0`. | -| `is_distributed()` | `world_size() > 1`. | -| `is_main_process()` | `global_rank() == 0`. | -| `pin_cuda_device()` | `"cuda:0"` in distributed runs (the CLI bootstrap pins `CUDA_VISIBLE_DEVICES` per worker, so each rank sees a single device); `None` in single-GPU mode. | - -Only the main process writes checkpoints, TensorBoard events, profiler -traces, and the `params/*.json` dump. RSL-RL handles the inter-rank gradient -all-reduce internally. - -## Profiling integration - -`maybe_profile` wraps `torch.profiler.profile` with a `schedule` and a -TensorBoard trace handler. The CLI surfaces eight flags on both `play` and -`train`: - -| CLI flag | `maybe_profile` kwarg | Env var fallback | Default | -|---|---|---|---| -| `--prof` | `enabled` | `GENELAB_PROFILE=1` | off | -| `--prof-out` | `out_dir` | `GENELAB_PROFILE_OUT` | `logs/torch_profile` | -| `--prof-wait` | `wait` | `GENELAB_PROFILE_WAIT` | `10` | -| `--prof-warmup` | `warmup` | `GENELAB_PROFILE_WARMUP` | `5` | -| `--prof-active` | `active` | `GENELAB_PROFILE_ACTIVE` | `10` | -| `--prof-repeat` | `repeat` | `GENELAB_PROFILE_REPEAT` | `2` | -| `--prof-record-shapes` | `record_shapes` | `GENELAB_PROFILE_RECORD_SHAPES=1` | off | -| `--prof-with-stack` | `with_stack` | `GENELAB_PROFILE_WITH_STACK=1` | off | - -The schedule fires one trace per `wait + warmup + active` steps, repeated -`repeat` times. The handler writes `*.pt.trace.json` + `*.tar.gz` under -`out_dir`; `uv run genelab prof open ` launches TensorBoard against it. - -```bash -uv run genelab train Genelab-Velocity-Flat-Unitree-G1-v0 \ - --prof --prof-active 3 --prof-repeat 1 --max-iterations 10 -uv run genelab prof open logs/torch_profile -``` - -## Failure modes worth knowing - -!!! warning "Mismatched agent cfg class" - `_dispatch_train` rejects any `task.cfg.agent` whose type is not - `RslRlOnPolicyRunnerCfg`. Extensions importing a hand-rolled runner cfg - that "happens to" have the same fields are not accepted. - -!!! warning "agent='trained' requires --checkpoint" - `play_task` raises `SystemExit("agent='trained' requires a --checkpoint - path")` when the checkpoint is omitted. Either pass `--checkpoint` or - pick `--agent zero` / `--agent random` for the visual smoke. - -!!! warning "torchrun relaunch loops" - `_relaunch_under_torchrun` only re-execs when `--gpus N` is set *and* - `TORCHELASTIC_RUN_ID` is absent. Running `torchrun ... -m genelab.cli - train --gpus 4` (with `--gpus` left in argv inside the torchrun env) - skips the relaunch because the env var is now present; this is intended. - The infinite loop only happens if `--gpus` is forwarded *without* - torchrun's env vars — typically when wrapping `genelab` inside another - launcher. - -!!! tip "Profile a short run first" - Profiler traces grow fast. Start with `--prof --prof-active 3 - --prof-repeat 1 --max-iterations 10` to size the trace before running a - full PPO schedule. +`genelab train TASK --gpus N` relaunches the current command under `torchrun`. The parent computes a +shared log directory so every rank writes into the same run. `--num_envs` is total across ranks; +`--num_envs_per_gpu` is per rank. -## See also +## Where to continue +- [Run RL Experiments](../best-practices/rl-experiments.md) - [Play and Train](../cli/play-train.md) -- [Profiling](../cli/profiling.md) -- [Managers and MDP terms](managers.md) +- [API Reference](../api/reference.md) diff --git a/docs/concepts/rl-runner.zh.md b/docs/concepts/rl-runner.zh.md index a11a477b..8214dd52 100644 --- a/docs/concepts/rl-runner.zh.md +++ b/docs/concepts/rl-runner.zh.md @@ -1,214 +1,38 @@ # RL runner -`genelab.rl` 是把已注册 task 跑成 PPO 训练 / 回放会话的薄 RSL-RL 集成层。CLI 子命令 -`play` 与 `train` 直接派发到下面两个入口;库调用者也可直接用。 +`genelab.rl` 把已注册 task 接到 RSL-RL。它刻意保持很薄:GeneLab 负责 task 解析、配置修改、 +VecEnv 适配、日志、profiling hook 和分布式启动 helper;RSL-RL 负责学习算法。 -## 模块表面 - -| 名字 | 用途 | -|---|---| -| `train_task(task_id, agent_cfg, **kwargs) -> Path` | 构造 task 的 env、用 `RslRlVecEnvWrapper` 包装、驱动 RSL-RL 的 `OnPolicyRunner.learn`。返回解析后的 log 目录。 | -| `play_task(task_id, **kwargs) -> None` | 构造 task 的 env(设置了 `play_env` 时优先用它),按 `agent` 选 policy,循环 `policy(obs) → env.step` 直到 viewer 关闭或达到 `max_steps`。支持 `"zero"` / `"random"` / `"trained"` 三种 policy。 | -| `AgentKind` | `Literal["zero", "random", "trained"]`。 | -| `RslRlOnPolicyRunnerCfg` | 顶层 PPO 配置(继承 `RslRlBaseRunnerCfg`);组合 actor / critic 的 `RslRlModelCfg` 与优化器的 `RslRlPpoAlgorithmCfg`。 | -| `RslRlVecEnvWrapper` | 把 `ManagerBasedRlEnv` 适配到 RSL-RL 的 `VecEnv` 协议。装了 RSL-RL 时动态成为 `rsl_rl.env.VecEnv` 的子类。 | -| `VecEnvBase` | 给非 RSL-RL 调用方的最小 VecEnv ABC。 | -| `maybe_profile(...)` | `torch.profiler` 的上下文管理器。关闭或非 rank 0 时 no-op。 | -| `profiler_enabled()` | `GENELAB_PROFILE=1` 时返回 `True`。 | - -`genelab.cli` 把 `play` / `train` 通过 `src/genelab/cli/__init__.py:384,417` 中的 -`_dispatch_play` / `_dispatch_train` 接到 `play_task` / `train_task`。 - -## train_task - -`train_task` 从已注册 task 上解析 env cfg、应用 CLI override、包装 env、跑 PPO 并 -返回 log 目录。 - -| 参数 | 默认 | 行为 | -|---|---|---| -| `task_id: str` | — | 必填。在 `TASKS` 注册表里查找。 | -| `agent_cfg: RslRlOnPolicyRunnerCfg` | — | 必填。CLI 从 task 的 `cfg.agent` 上拿;库调用者直接传。 | -| `num_envs: int \| None` | `None` | 覆盖 `env_cfg.simulation.num_envs`。 | -| `max_iterations: int \| None` | `None` | 覆盖 `agent_cfg.max_iterations`。 | -| `seed: int \| None` | `None` | 同时设置 `env_cfg.seed` 与 `agent_cfg.seed`。 | -| `log_root: Path \| None` | `Path("logs/rsl_rl")` | 在该目录下创建 `/`。 | -| `log_dir: Path \| None` | `None` | 预解析的最终目录,优先级高于 `log_root`。torchrun 重启路径用这个让每个 rank 落在同一文件夹。 | -| `resume_from: Path \| None` | `None` | `runner.learn` 之前要加载的已有 checkpoint。 | -| `prof`、`prof_out`、`prof_wait`、`prof_warmup`、`prof_active`、`prof_repeat`、`prof_record_shapes`、`prof_with_stack` | `None` | 转发给 `maybe_profile`。每个 kwarg 覆盖对应的 `GENELAB_PROFILE_*` env var;参见下面 "Profiler 集成" 节。 | - -Rank 0 的副作用: - -- 在 `/params/env.json` 与 `/params/agent.json` 中递归 JSON dump - 两份 cfg。 -- RSL-RL 按 `agent_cfg.save_interval` 写 `/model_.pt`。 -- TensorBoard event 直接落到 `/` 下。 - -打开 profiler 时,`train_task` 会 hook `RslRlVecEnvWrapper.step`,使 profiler -schedule 每 env step 推进一次。若想以 PPO iteration 表达 schedule,把 -`wait` / `warmup` / `active` 乘以 `agent_cfg.num_steps_per_env`。 - -## play_task - -`play_task` 构造 env(设置 `task_cfg.play_env` 时优先),按 `agent` 选 policy,循环 -直到 viewer 关闭。 - -| 参数 | 默认 | 行为 | -|---|---|---| -| `task_id: str` | — | 必填。 | -| `checkpoint: Path \| None` | `None` | `agent="trained"` 时必填。 | -| `num_envs: int \| None` | `None` | 覆盖 `env_cfg.simulation.num_envs`。 | -| `agent: "zero" \| "random" \| "trained" \| None` | `None` | 给了 `checkpoint` 默认 `"trained"`,否则 `"zero"`。 | -| `agent_cfg: RslRlOnPolicyRunnerCfg \| None` | `None` | 只有 `agent="trained"` 才需要;缺省 fallback 到 `task.cfg.agent`。 | -| `deterministic: bool` | `True` | 传给 RSL-RL 推理辅助函数。 | -| `max_steps: int \| None` | `None` | rollout 上限。`None` 表示跑到 viewer 关闭。 | -| `prof*` | `None` | 语义同 `train_task`。 | - -Play 循环每步检测 `env.viewer_closed`,关 Genesis viewer 时干净退出。`env.close()` -放在 `finally` 块里。 - -## RslRlOnPolicyRunnerCfg - -扩展通过 `TaskCfg.agent` 注册的顶层 dataclass。`train` 依赖 -`class_name="OnPolicyRunner"`;扩展 cfg 用了其他 runner 类会在 dispatch 时被拒。 +## 训练流程 +```text +TASKS.get(task_id) +└── TaskCfg.env + TaskCfg.agent + └── ManagerBasedRlEnv + └── RslRlVecEnvWrapper + └── rsl_rl OnPolicyRunner.learn() ``` -RslRlOnPolicyRunnerCfg -├──(继承自 RslRlBaseRunnerCfg) -│ seed=42、num_steps_per_env=24、max_iterations=300 -│ obs_groups={"actor": ("policy",), "critic": ("critic",)} -│ save_interval=50、experiment_name="exp1"、run_name="" -│ logger="tensorboard" | "wandb" -│ wandb_project="genelab"、wandb_tags=() -│ resume=False、load_run=".*"、load_checkpoint="model_.*.pt" -│ clip_actions: float | None、upload_model=False -├── class_name="OnPolicyRunner" -├── actor: RslRlModelCfg -├── critic: RslRlModelCfg -└── algorithm: RslRlPpoAlgorithmCfg -``` - -`RslRlModelCfg` 旋钮(actor / critic 各一份):`hidden_dims=(128, 128, 128)`、 -`activation="elu"`、`obs_normalization=False`,可选 `cnn_cfg` / `distribution_cfg` / -`rnn_type` / `rnn_hidden_dim` / `rnn_num_layers`、`class_name="MLPModel"`。 -只有 `class_name`、`hidden_dims`、`activation`、`obs_normalization`、 -`distribution_cfg` 会进入 MLP 构造函数;其余字段被 `_prune_model_cfg` 过滤掉。 - -`RslRlPpoAlgorithmCfg` 旋钮:`num_learning_epochs=5`、`num_mini_batches=4`、 -`learning_rate=1e-3`、`schedule="adaptive"|"fixed"`、`gamma=0.99`、`lam=0.95`、 -`entropy_coef=0.005`、`desired_kl=0.01`、`max_grad_norm=1.0`、 -`value_loss_coef=1.0`、`use_clipped_value_loss=True`、`clip_param=0.2`、 -`optimizer="adam"|"adamw"|"sgd"|"rmsprop"`、`class_name="PPO"`。 - -## AgentKind -`AgentKind = Literal["zero", "random", "trained"]`。`play_task` 按 `kind` 挑 policy -可调用: +main process 写入 `params/env.json`、`params/agent.json`、TensorBoard event、profiler trace 和 checkpoint。 -| Kind | Policy | 适用场景 | -|---|---|---| -| `"zero"` | 返回 `torch.zeros((num_envs, num_actions))`。 | 训练前的视觉 smoke;被动姿态 viewer rollout。 | -| `"random"` | 返回 `2.0 * rand(...) - 1.0`(uniform `[-1, 1]`)。 | sanity-check action 边界与 reward shaping。 | -| `"trained"` | 加载 checkpoint,实例化 `OnPolicyRunner`,调 `runner.get_inference_policy()`。 | 看训好的 policy。需要 `checkpoint`。 | +## 回放流程 -## VecEnv wrapper +`play_task` 在存在 `TaskCfg.play_env` 时优先使用它。策略来源: -`RslRlVecEnvWrapper` 把 `ManagerBasedRlEnv` 适配到 RSL-RL 的 `OnPolicyRunner` -期望的接口: - -| 属性 / 方法 | 来源 | +| Agent | 来源 | |---|---| -| `num_envs`、`device`、`max_episode_length` | `env.num_envs`、`env.device`、`env.max_episode_length`。 | -| `num_actions` | `env.action_manager.total_action_dim`。 | -| `num_obs`、`num_privileged_obs` | `"policy"` / `"critic"` 观测组的末轴维数,构造时先跑一次 `observation_manager.compute()` 取出。 | -| `reset()` | `env.reset()`。装了 `tensordict` 时把 obs 包成 `TensorDict`;否则原样返回 plain `dict`。 | -| `step(actions)` | 设了 `clip_actions` 就先 clip,调 `env.step`,打包 `(obs, reward, dones, extras)`,其中 `dones = terminated \| truncated`(long)、`extras["time_outs"] = truncated`。 | -| `episode_length_buf`、`cfg`、`unwrapped` | 透传到底层 env。 | +| `zero` | 返回零动作。 | +| `random` | 均匀随机动作。 | +| `trained` | 加载 checkpoint,并调用 RSL-RL inference policy。 | -Wrapper 在 import 时通过 `_attach_rsl_rl_base()` 动态成为 `rsl_rl.env.VecEnv` -的子类。没装 RSL-RL(例如 CPU-only 主机上的单元测试)时回退到拥有同样属性的 plain -对象。 +Genesis viewer 关闭或达到 `max_steps` 时,回放会干净退出。 ## 分布式训练 -CLI 的 `train` 上有 `--gpus N` 旗标。`cli/__init__.py:457` 的 -`_relaunch_under_torchrun` 在父进程里改写它: - -1. 在父进程预解析 log 目录为 `//`,让每个 rank - 拿到同一路径。 -2. 从 `sys.argv` 里剥掉 `--gpus N` 避免重启循环。 -3. 原始 argv 里没带 `--log-dir` 时追加 `--log-dir <预解析路径>`。 -4. `execvp`:`python -m torch.distributed.run --standalone --nproc_per_node=N - -m genelab.cli `。 - -```bash -uv run genelab train Genelab-Velocity-Flat-Unitree-G1-v0 --gpus 4 --num-envs 4096 -``` - -每个 rank 内部,`genelab.rl.distributed` 从环境读 `WORLD_SIZE` / `LOCAL_RANK` / -`RANK`: - -| Helper | 返回 | -|---|---| -| `world_size()` | `int(os.environ["WORLD_SIZE"])` 或 `1`。 | -| `local_rank()` | `int(os.environ["LOCAL_RANK"])` 或 `0`。 | -| `global_rank()` | `int(os.environ["RANK"])` 或 `0`。 | -| `is_distributed()` | `world_size() > 1`。 | -| `is_main_process()` | `global_rank() == 0`。 | -| `pin_cuda_device()` | 分布式 run 中返回 `"cuda:0"`(CLI bootstrap 已按 rank 钉 `CUDA_VISIBLE_DEVICES`,每个 rank 只看见一张卡);单 GPU 模式返回 `None`。 | - -只有 main process 写 checkpoint、TensorBoard event、profiler trace 与 -`params/*.json` 转储。RSL-RL 内部跨 rank all-reduce 梯度。 - -## Profiler 集成 - -`maybe_profile` 用一个 `schedule` 与 TensorBoard trace handler 包住 -`torch.profiler.profile`。CLI 在 `play` 与 `train` 上各暴露 8 个 flag: - -| CLI flag | `maybe_profile` kwarg | Env var fallback | 默认 | -|---|---|---|---| -| `--prof` | `enabled` | `GENELAB_PROFILE=1` | off | -| `--prof-out` | `out_dir` | `GENELAB_PROFILE_OUT` | `logs/torch_profile` | -| `--prof-wait` | `wait` | `GENELAB_PROFILE_WAIT` | `10` | -| `--prof-warmup` | `warmup` | `GENELAB_PROFILE_WARMUP` | `5` | -| `--prof-active` | `active` | `GENELAB_PROFILE_ACTIVE` | `10` | -| `--prof-repeat` | `repeat` | `GENELAB_PROFILE_REPEAT` | `2` | -| `--prof-record-shapes` | `record_shapes` | `GENELAB_PROFILE_RECORD_SHAPES=1` | off | -| `--prof-with-stack` | `with_stack` | `GENELAB_PROFILE_WITH_STACK=1` | off | - -Schedule 每 `wait + warmup + active` 步出一份 trace,重复 `repeat` 次。Handler 在 -`out_dir` 下写 `*.pt.trace.json` + `*.tar.gz`;`uv run genelab prof open ` -对其起 TensorBoard。 - -```bash -uv run genelab train Genelab-Velocity-Flat-Unitree-G1-v0 \ - --prof --prof-active 3 --prof-repeat 1 --max-iterations 10 -uv run genelab prof open logs/torch_profile -``` - -## 已知失效模式 - -!!! warning "agent cfg 类型不匹配" - `_dispatch_train` 拒绝任何 `task.cfg.agent` 类型不是 `RslRlOnPolicyRunnerCfg` - 的 task。"字段刚好一样"的自造 runner cfg 不被接受。 - -!!! warning "agent='trained' 需要 --checkpoint" - 缺 checkpoint 时 `play_task` 抛 `SystemExit("agent='trained' requires a - --checkpoint path")`。要么传 `--checkpoint`,要么挑 `--agent zero` / - `--agent random` 做视觉 smoke。 - -!!! warning "torchrun 重启回环" - `_relaunch_under_torchrun` 仅在 `--gpus N` 出现 *且* `TORCHELASTIC_RUN_ID` - 缺失时重新 exec。手工跑 `torchrun ... -m genelab.cli train --gpus 4`(在 - torchrun 内部 argv 里仍保留 `--gpus`)会跳过重启 —— 因为 env var 已存在;这是 - 预期行为。无限循环只在 `--gpus` 被转发但 *没有* torchrun 环境变量时出现, - 通常是 `genelab` 被其他 launcher 套进去导致的。 - -!!! tip "先 profile 一段短 run" - Profiler trace 增长很快。从 `--prof --prof-active 3 --prof-repeat 1 - --max-iterations 10` 起步,先看 trace 体量再跑完整 PPO schedule。 +`genelab train TASK --gpus N` 会把当前命令重新拉起到 `torchrun` 下。父进程预先计算共享日志目录,让每个 rank 写入同一个 run。`--num_envs` 是所有 rank 总数;`--num_envs_per_gpu` 是每 rank 数量。 -## See also +## 继续阅读 -- [Play and Train](../cli/play-train.md) -- [Profiling](../cli/profiling.md) -- [Managers and MDP terms](managers.md) +- [运行 RL 实验](../best-practices/rl-experiments.md) +- [play 与 train](../cli/play-train.md) +- [API 参考](../api/reference.md) diff --git a/docs/concepts/scene.en.md b/docs/concepts/scene.en.md index 7ee4dd37..a3a82e99 100644 --- a/docs/concepts/scene.en.md +++ b/docs/concepts/scene.en.md @@ -1,160 +1,36 @@ -# Scene and entities +# Scene and Entities -`genelab.scene.InteractiveScene` is the composition root that owns a Genesis -`gs.Scene` together with the articulated robot, rigid props, optional terrain, and -the mouse-interaction plugin. `ManagerBasedRlEnv` defers all sim-level orchestration -to the scene; the env layer only manages the seven MDP terms. +`InteractiveScene` is GeneLab's Genesis scene owner. It translates declarative configs into live +Genesis handles and exposes Isaac Lab-shaped entity wrappers to the rest of the environment. -## Composing a Genesis scene +## Scene boundary -A scene is built from two cfg objects passed to the env. `SimulationCfg` carries the -Genesis runtime knobs (timestep, parallel-env count, viewer toggle); `InteractiveSceneCfg` -carries the composition (entities, terrain, sensors, mouse plugin, BatchRenderer -toggle). The scene parses the cfg, allocates wrapper objects, then defers the actual -Genesis allocation to the `build()` call. +`InteractiveSceneCfg` describes what should exist: env spacing, entities, terrain, sensors, +recordings, viewer interaction, and batch rendering. `InteractiveScene` owns what actually exists: +the Genesis `Scene`, articulations, rigid objects, sensors, terrain importer, recorder bridge, and +viewer state. -```python -from genelab.asset_zoo import CartpoleCfg -from genelab.configs import InteractiveSceneCfg, SimulationCfg -from genelab.envs.manager_based_rl_env import ManagerBasedRlEnvCfg +This separation keeps configs serializable and lets tasks be inspected before Genesis starts. -env_cfg = ManagerBasedRlEnvCfg( - simulation=SimulationCfg(num_envs=4096, dt=0.005, substeps=1, vis=False), - scene=InteractiveSceneCfg(env_spacing=(2.5, 2.5)), - robot=CartpoleCfg(), -) -``` +## Entities -## Lifecycle +| Entity | Purpose | +|---|---| +| `Articulation` | Robot wrapper with joint/link names, default joint state, limits, and refreshed `RobotState`. | +| `RigidObject` | Non-articulated object wrapper. | +| `RobotState` | Batched tensors read by observations, rewards, sensors, and events. | -`InteractiveScene` follows a strict three-step lifecycle, enforced at runtime: +`ManagerBasedRlEnv` adds the configured robot as the `"robot"` articulation and exposes convenient +properties such as `env.robot_state`, `env.joint_names`, `env.link_names`, and `env.scene`. -1. **Construct**: `InteractiveScene(sim_cfg, scene_cfg, device_hint=...)` parses the - cfgs and allocates wrapper objects for every entity declared in - `scene_cfg.entities`. No Genesis import yet. -2. **Add entities**: `add_entity(name, cfg)` registers extra entities — the env uses - this to inject `ManagerBasedRlEnvCfg.robot` under the name `"robot"`. Calling - `add_entity` after `build()` raises `RuntimeError`. -3. **Build**: `build()` imports Genesis, initialises the backend, creates the - `gs.Scene`, spawns ground / terrain / entities / cameras, attaches the mouse - plugin when configured, then calls `gs_scene.build(n_envs, env_spacing)`. +## Why wrappers matter -`ManagerBasedRlEnv.__init__` runs all three steps in order and post-build binds every -articulation so the per-joint and per-link tensors land. +Genesis APIs and Isaac Lab-style task code use different vocabulary. Wrappers isolate that +difference: MDP terms read stable GeneLab properties while the backend integration handles Genesis +details. -## SimulationCfg +## Where to continue -| Field | Default | Meaning | -|---|---|---| -| `vis` | `False` | Open the Genesis viewer. Forces `num_envs=1` in practice; the viewer can only render env 0. | -| `gpu` | `False` | Use the CUDA backend (`gs.gpu`). Required for `BatchRenderer`. | -| `steps` | `240` | Default number of steps the CLI `play` runner takes when no override is given. | -| `dt` | `0.01` | Physics timestep in seconds. | -| `substeps` | `4` | Genesis substeps per physics step. | -| `num_envs` | `1` | Parallel-env count. Most managers tensorise across this dimension. | - -## InteractiveSceneCfg - -| Field | Default | Meaning | -|---|---|---| -| `env_spacing` | `(2.0, 2.0)` | Per-env XY offset Genesis applies when `num_envs > 1`. | -| `sensors` | `()` | Tuple of `SensorCfg` instances built and bound after the scene is constructed. | -| `mouse_interaction` | `False` | Attach the GeneLab mouse-drag plugin to the viewer (only effective with `vis=True`). | -| `entities` | `{}` | Additional `ArticulationCfg` / `RigidObjectCfg` keyed by name. The env injects the robot as `"robot"`. | -| `terrain` | `None` | A `TerrainGeneratorCfg`. Default `None` adds a flat `gs.morphs.Plane`. | -| `batch_render` | `False` | Pass `gs.renderers.BatchRenderer(use_rasterizer=False)` to `gs.Scene`. Required for `CameraSensor` to emit per-env RGB-D tensors. Linux x86-64 + CUDA only. | - -## Articulation - -`Articulation` wraps an articulated robot. Construct from `ArticulationCfg` and add -to the scene via `scene.add_entity("robot", cfg)` (or set -`ManagerBasedRlEnvCfg.robot` and let the env wire it). - -| Field | Type | Meaning | -|---|---|---| -| `mjcf_path` | `str` | Absolute path to a MuJoCo XML file. | -| `init_pos` | `tuple[float, float, float]` | World-frame spawn offset in metres. | -| `init_quat` | `tuple[float, float, float, float]` | World-frame spawn orientation (wxyz). | -| `default_joint_pos` | `dict[str, float]` | Regex-keyed default joint position; last match wins. | -| `actuators` | `dict[str, ActuatorBaseCfg]` | Joint groups. Every actuated joint must be covered by exactly one group. | -| `foot_link_names` | `tuple[str, ...]` | Optional metadata for downstream MDP terms (e.g. contact sensors). | - -The pre-M3 `joint_kp`, `joint_kv`, and dict-shaped `action_scale` knobs are -removed — configure equivalent groups through `actuators`. Passive joints take an -explicit zero-gain `ImplicitPDActuatorCfg` so the topology stays visible. - -```python -from genelab.actuator import ImplicitPDActuatorCfg -from genelab.entity import ArticulationCfg - -cartpole_cfg = ArticulationCfg( - mjcf_path="/path/to/cartpole.xml", - init_pos=(0.0, 0.0, 1.5), - default_joint_pos={"cart_slide": 0.0, "pole_hinge": 0.0}, - actuators={ - "cart": ImplicitPDActuatorCfg( - target_names_expr=("cart_slide",), stiffness=80.0, damping=8.0, - ), - "pole": ImplicitPDActuatorCfg( - target_names_expr=("pole_hinge",), stiffness=0.0, damping=0.0, - ), - }, -) -``` - -## RigidObject - -`RigidObject` represents a non-articulated body. No actuators, no per-step state — it -is geometry that participates in collisions and contacts but is never driven. Spawn -via `scene.add_entity("name", RigidObjectCfg(...))` before `build()`. - -| Field | Type | Meaning | -|---|---|---| -| `morph` | `Literal["plane", "box", "sphere", "mesh", "mjcf"]` | Genesis morph selector. | -| `file` | `str \| None` | Path to mesh / MJCF; required for `"mesh"` / `"mjcf"`. | -| `size` | `tuple[float, ...]` | `(x, y, z)` for box, `(radius,)` for sphere. | -| `init_pos`, `init_quat` | tuple | World-frame spawn pose (wxyz quat). | -| `fixed` | `bool` | When `True`, the body is welded to the world frame. | - -Typical uses are static obstacles, target markers, and passive payloads. For -articulated joints — even one — use `Articulation` instead. - -## Failure modes worth knowing - -!!! warning "`add_entity` after `build`" - `InteractiveScene.add_entity` raises `RuntimeError` once `build()` has run. - Construct the env, then add every extra entity, then let the env call `build()`. - -!!! warning "Empty `actuators` dict" - `Articulation.bind` rejects an empty `actuators` dict. Every actuated joint must - be covered by exactly one group; unmatched or doubly-matched joints raise - `ValueError` with the conflicting joint names listed. - -!!! warning "Genesis backend mismatch" - `SimulationCfg.gpu=True` requires a working CUDA install. On macOS or CPU-only - Linux, leave `gpu=False`; the CPU backend handles single-env play loops fine. - `batch_render=True` further requires Linux x86-64 + CUDA + Madrona — there is no - CPU fallback. - -!!! note "Viewer and parallel envs" - `SimulationCfg.vis=True` is only meaningful with `num_envs=1`. The viewer - renders env 0 regardless, so leave `num_envs` high for training and switch to a - dedicated `play_env` with `num_envs=1` for visual inspection. - -!!! tip "Viewer closed mid-rollout" - When the user closes the Genesis viewer, the kernel catches - `GenesisException("Viewer closed.")` inside `InteractiveScene.step` and flips - `env.viewer_closed = True`. Subsequent `env.step` calls become no-ops. Loop - drivers should poll the flag instead of writing their own try / except: - ```python - for step in range(max_steps): - obs, *_ = env.step(action) - if env.viewer_closed: - break - ``` - -## See also - -- [Configs](configs.md) +- [Module Map](../reference/module-map.md) +- [Sensors](sensors.md) - [Actuators](actuators.md) -- [Asset zoo](asset_zoo.md) diff --git a/docs/concepts/scene.zh.md b/docs/concepts/scene.zh.md index e3e8afd5..fe1c53a3 100644 --- a/docs/concepts/scene.zh.md +++ b/docs/concepts/scene.zh.md @@ -1,152 +1,33 @@ # 场景与实体 -`genelab.scene.InteractiveScene` 是组合根 (composition root),持有一个 Genesis `gs.Scene` -以及关节机器人、刚体道具、可选地形、鼠标交互插件。`ManagerBasedRlEnv` 把所有 sim 层 -编排都交给 scene;env 层只管 7 个 MDP term。 +`InteractiveScene` 是 GeneLab 的 Genesis scene owner。它把声明式配置转换成 live Genesis handle, +并向环境其他部分暴露 Isaac Lab 风格的 entity wrapper。 -## 组合 Genesis 场景 +## Scene 边界 -scene 从两个传给 env 的 cfg 对象构造。`SimulationCfg` 携带 Genesis 运行时旋钮 (时间步、 -并行 env 数、viewer 开关);`InteractiveSceneCfg` 携带组合内容 (entities、terrain、sensors、 -mouse 插件、BatchRenderer 开关)。scene 解析 cfg、分配 wrapper 对象,真正的 Genesis 分配 -延后到 `build()` 调用。 +`InteractiveSceneCfg` 描述应该存在什么:env spacing、entities、terrain、sensors、recordings、 +viewer interaction、batch rendering。`InteractiveScene` 持有实际存在的东西:Genesis `Scene`、 +articulation、rigid object、sensor、terrain importer、recorder bridge 和 viewer 状态。 -```python -from genelab.asset_zoo import CartpoleCfg -from genelab.configs import InteractiveSceneCfg, SimulationCfg -from genelab.envs.manager_based_rl_env import ManagerBasedRlEnvCfg +这种分离让配置可序列化,也让任务能在 Genesis 启动前被检查。 -env_cfg = ManagerBasedRlEnvCfg( - simulation=SimulationCfg(num_envs=4096, dt=0.005, substeps=1, vis=False), - scene=InteractiveSceneCfg(env_spacing=(2.5, 2.5)), - robot=CartpoleCfg(), -) -``` +## Entities -## 生命周期 +| Entity | 用途 | +|---|---| +| `Articulation` | 机器人 wrapper,包含 joint/link 名、默认 joint state、limits、刷新后的 `RobotState`。 | +| `RigidObject` | 非关节物体 wrapper。 | +| `RobotState` | observation、reward、sensor、event 读取的 batched tensor。 | -`InteractiveScene` 遵循严格的三步生命周期,运行时强校验: +`ManagerBasedRlEnv` 把配置中的机器人作为 `"robot"` articulation 加入,并暴露 +`env.robot_state`、`env.joint_names`、`env.link_names`、`env.scene` 等便捷属性。 -1. **构造**:`InteractiveScene(sim_cfg, scene_cfg, device_hint=...)` 解析 cfg 并为 - `scene_cfg.entities` 里每个实体分配 wrapper。此刻还没 import Genesis。 -2. **追加实体**:`add_entity(name, cfg)` 注册额外实体——env 用它把 - `ManagerBasedRlEnvCfg.robot` 以 `"robot"` 名注入。`build()` 之后再调 - `add_entity` 会抛 `RuntimeError`。 -3. **构建**:`build()` 导入 Genesis、初始化 backend、创建 `gs.Scene`、生成地面 / 地形 / - 实体 / 相机,按需挂上鼠标插件,最后 `gs_scene.build(n_envs, env_spacing)`。 +## 为什么需要 wrapper -`ManagerBasedRlEnv.__init__` 顺序跑完三步,build 之后再 bind 每个 articulation,让 -per-joint / per-link tensor 落位。 +Genesis API 和 Isaac Lab 风格任务代码使用的词汇不同。wrapper 隔离这种差异:MDP term 读取稳定的 GeneLab 属性,后端集成负责 Genesis 细节。 -## SimulationCfg +## 继续阅读 -| 字段 | 默认 | 含义 | -|---|---|---| -| `vis` | `False` | 打开 Genesis viewer。实际只配合 `num_envs=1`;viewer 只能渲染 env 0。 | -| `gpu` | `False` | 使用 CUDA backend (`gs.gpu`)。`BatchRenderer` 需要它。 | -| `steps` | `240` | CLI `play` 在没指定 override 时默认跑的步数。 | -| `dt` | `0.01` | 物理时间步 (秒)。 | -| `substeps` | `4` | 每个物理步内的 Genesis 子步数。 | -| `num_envs` | `1` | 并行 env 数。大多数 manager 在这个维度上向量化。 | - -## InteractiveSceneCfg - -| 字段 | 默认 | 含义 | -|---|---|---| -| `env_spacing` | `(2.0, 2.0)` | `num_envs > 1` 时 Genesis 在 XY 上为每个 env 留的间距。 | -| `sensors` | `()` | scene 构造完后批量 build + bind 的 `SensorCfg` 元组。 | -| `mouse_interaction` | `False` | 给 viewer 挂上 GeneLab 的鼠标拖拽插件 (仅在 `vis=True` 时有效)。 | -| `entities` | `{}` | 按名字索引的额外 `ArticulationCfg` / `RigidObjectCfg`。env 会以 `"robot"` 名注入主机器人。 | -| `terrain` | `None` | `TerrainGeneratorCfg`;默认 `None` 时铺一张 `gs.morphs.Plane` 平面。 | -| `batch_render` | `False` | 把 `gs.renderers.BatchRenderer(use_rasterizer=False)` 传给 `gs.Scene`。`CameraSensor` 输出 per-env RGB-D 张量必需。仅支持 Linux x86-64 + CUDA。 | - -## Articulation - -`Articulation` 包装一个关节机器人。从 `ArticulationCfg` 构造,再通过 -`scene.add_entity("robot", cfg)` 加入 scene (或者直接设 `ManagerBasedRlEnvCfg.robot`,让 -env 自动接管)。 - -| 字段 | 类型 | 含义 | -|---|---|---| -| `mjcf_path` | `str` | MuJoCo XML 文件的绝对路径。 | -| `init_pos` | `tuple[float, float, float]` | 世界系初始位移 (米)。 | -| `init_quat` | `tuple[float, float, float, float]` | 世界系初始姿态 (wxyz 四元数)。 | -| `default_joint_pos` | `dict[str, float]` | 正则键控的默认关节位置;按 last-match-wins。 | -| `actuators` | `dict[str, ActuatorBaseCfg]` | 关节分组。每个被驱动关节必须恰被一个分组覆盖。 | -| `foot_link_names` | `tuple[str, ...]` | 给下游 MDP term (例如 contact sensor) 的可选元数据。 | - -M3 之前的 `joint_kp` / `joint_kv` / dict 形 `action_scale` 旋钮已经移除——同等行为请 -通过 `actuators` 配。被动关节用一个零增益的 `ImplicitPDActuatorCfg` 显式覆盖,保持 -拓扑可见。 - -```python -from genelab.actuator import ImplicitPDActuatorCfg -from genelab.entity import ArticulationCfg - -cartpole_cfg = ArticulationCfg( - mjcf_path="/path/to/cartpole.xml", - init_pos=(0.0, 0.0, 1.5), - default_joint_pos={"cart_slide": 0.0, "pole_hinge": 0.0}, - actuators={ - "cart": ImplicitPDActuatorCfg( - target_names_expr=("cart_slide",), stiffness=80.0, damping=8.0, - ), - "pole": ImplicitPDActuatorCfg( - target_names_expr=("pole_hinge",), stiffness=0.0, damping=0.0, - ), - }, -) -``` - -## RigidObject - -`RigidObject` 表示一个非关节刚体。无 actuator、无 per-step 状态——它就是几何,参与碰撞 -与接触,但永远不被驱动。`build()` 之前通过 `scene.add_entity("name", RigidObjectCfg(...))` -加入。 - -| 字段 | 类型 | 含义 | -|---|---|---| -| `morph` | `Literal["plane", "box", "sphere", "mesh", "mjcf"]` | Genesis morph 选择。 | -| `file` | `str \| None` | mesh / MJCF 路径;`"mesh"` / `"mjcf"` 必填。 | -| `size` | `tuple[float, ...]` | box 是 `(x, y, z)`;sphere 是 `(radius,)`。 | -| `init_pos`、`init_quat` | tuple | 世界系初始位姿 (wxyz quat)。 | -| `fixed` | `bool` | `True` 时把刚体焊死在世界系。 | - -典型用法是静态障碍、目标 marker、被动负载。只要有一个关节就用 `Articulation`,不要用 -`RigidObject`。 - -## 已知失效模式 - -!!! warning "`build` 之后追加实体" - `build()` 之后再调 `InteractiveScene.add_entity` 会抛 `RuntimeError`。先构造 env,把 - 所有额外实体加完,再让 env 调用 `build()`。 - -!!! warning "空 `actuators` 字典" - `Articulation.bind` 拒绝空 `actuators`。每个被驱动关节必须被恰一个分组覆盖;未匹配 - 或被双重匹配的关节会带着冲突关节名抛 `ValueError`。 - -!!! warning "Genesis backend 不匹配" - `SimulationCfg.gpu=True` 需要可用的 CUDA 安装。macOS / 纯 CPU Linux 上保持 - `gpu=False`;CPU backend 跑单 env play 循环没问题。`batch_render=True` 额外要求 - Linux x86-64 + CUDA + Madrona——没有 CPU 回退。 - -!!! note "Viewer 与并行 env" - `SimulationCfg.vis=True` 仅在 `num_envs=1` 时有意义。viewer 只渲染 env 0,所以训练 - 时把 `num_envs` 设高,再单独配一份 `num_envs=1` 的 `play_env` 用于人眼检查。 - -!!! tip "Rollout 中途关闭 viewer" - 用户关掉 Genesis viewer 后,内核会在 `InteractiveScene.step` 里捕获 - `GenesisException("Viewer closed.")` 并把 `env.viewer_closed` 置 `True`,后续 - `env.step` 自动 no-op。上层循环只需轮询这个 flag,不必自己写 try / except: - ```python - for step in range(max_steps): - obs, *_ = env.step(action) - if env.viewer_closed: - break - ``` - -## See also - -- [Configs](configs.md) -- [Actuators](actuators.md) -- [Asset zoo](asset_zoo.md) +- [模块地图](../reference/module-map.md) +- [传感器](sensors.md) +- [执行器](actuators.md) diff --git a/docs/concepts/sensors.en.md b/docs/concepts/sensors.en.md index 38bc37bf..8e7ba0b6 100644 --- a/docs/concepts/sensors.en.md +++ b/docs/concepts/sensors.en.md @@ -1,284 +1,45 @@ # Sensors -Genesis does not parse the MJCF `` block, so GeneLab introduces a backend-agnostic -sensor abstraction. The interface mirrors mjlab's `SensorCfg` / `Sensor[T]`, but every concrete -sensor reads from the env's `RobotState` instead of MuJoCo sensordata — observation and reward -terms transfer between backends without ceremony. +Genesis does not expose every signal through an MJCF sensor block, so GeneLab provides a +backend-aware sensor abstraction. Sensors produce cached data that observations, rewards, +recordings, and custom code can consume. ## Lifecycle -`bind(env)` runs once at construction. Each control step, `update(dt)` invalidates the cache; -the first access to `data` triggers `_compute_data` lazily. `reset(env_ids)` invalidates the -cache and lets stateful sensors clear per-env buffers. +Sensors are declared in `InteractiveSceneCfg.sensors`. `ManagerBasedRlEnv` builds and binds them +after the scene exists. Each control step invalidates cached data; the first access to `sensor.data` +computes fresh output. Reset clears per-env sensor state. -```python -class Sensor[T](ABC): - def bind(self, env: "ManagerBasedRlEnv") -> None: ... - @property - def data(self) -> T: ... - def update(self, dt: float) -> None: ... - def reset(self, env_ids: torch.Tensor | None = None) -> None: ... - @abstractmethod - def _compute_data(self) -> T: ... -``` +This lazy cache keeps repeated observation and reward reads consistent within one control step. -The env wires the lifecycle automatically: sensors built from `InteractiveSceneCfg.sensors` -get `update` called after each articulation refresh and `reset` called from inside -`_reset_idx`, so reward and observation terms always see fresh sensor data. +## Built-in sensor families -## Registering on a scene +| Sensor | Use case | +|---|---| +| `BodyVelocitySensor` | Link linear or angular velocity at an offset. | +| `IMUSensor` | Orientation, projected gravity, linear/angular acceleration. | +| `CameraSensor` | RGB-D camera attached to a link. | +| `ContactSensor` | Per-link contact force and optional air/contact time. | +| `FrameTransformerSensor` | Target frame poses relative to a source frame. | +| `RayCastSensor` | Grid, ring, or hemisphere ray patterns. | +| `TerrainHeightSensor` | Downward height scan for terrain-aware locomotion. | +| `SelfContactSensor` | Self-collision/contact signals. | +| `RootAngularMomentumSensor` | Root angular momentum diagnostics. | -`InteractiveSceneCfg.sensors` is a tuple of `SensorCfg`. `ManagerBasedRlEnv.__init__` calls -`build()` on each cfg and binds the resulting sensor to the env. Access at runtime is via -`env.sensors[name].data`. +## Sensor data in MDP terms -```python -from genelab.configs import InteractiveSceneCfg, SimulationCfg -from genelab.sensor import BodyVelocitySensorCfg, ContactSensorCfg +Observation terms can read sensors through `mdp.sensor_data` or a custom callable. Rewards and +metrics can access `env.sensors[name].data` directly. -simulation = SimulationCfg(num_envs=4096) -scene = InteractiveSceneCfg( - sensors=( - BodyVelocitySensorCfg( - name="imu_lin_vel", - link_name="pelvis", - offset=(0.04525, 0.0, -0.08339), - measure="lin_vel", - ), - ContactSensorCfg( - name="feet_ground_contact", - link_names=("left_ankle_roll_link", "right_ankle_roll_link"), - track_air_time=True, - ), - ), -) -``` +Keep sensor names stable; they become config paths, recording sources, and term params. -## Built-in sensors +## Camera caveat -### BodyVelocitySensor +Parallel RGB-D rendering requires CUDA and `InteractiveSceneCfg.batch_render=True`. Treat camera +tasks as a separate hardware profile from light state-based RL tasks. -Mirrors a MuJoCo `` / `` on a site rigidly attached to a robot link. Returns -the linear or angular velocity of the site in the link's body frame, with a configurable -lever-arm offset and optional per-env uniform bias. +## Where to continue -| Field | Type | Meaning | -|-------|------|---------| -| `link_name` | `str` | Link the site is rigidly attached to. | -| `offset` | `tuple[float, float, float]` | Site position in the link's local frame. Ignored for `ang_vel`. | -| `measure` | `Literal["lin_vel", "ang_vel"]` | Velocimeter (linear) or gyro (angular). | -| `bias_range` | `tuple[float, float] \| None` | Uniform per-env bias resampled on `reset`. | - -The velocimeter math is `v_site = v_link + ω × (R_link · offset)` rotated into the body frame — -matching MuJoCo's lever-arm convention so a GeneLab-trained policy reads the same signal as the -mjlab reference. - -### IMUSensor - -Inertial Measurement Unit at a site rigidly attached to a link. Outputs orientation -(`link_quat_w`), body-frame projected unit gravity, and body-frame linear / angular -acceleration. Accelerations are computed by finite difference of the world-frame velocity -buffers; the first control step after every `reset` returns zero acceleration to avoid a -spurious spike from a stale `prev` value — document this in any reward / observation term -that consumes `lin_acc_b` or `ang_acc_b` near reset boundaries. - -| Field | Type | Meaning | -|-------|------|---------| -| `link_name` | `str` | Link the IMU is rigidly attached to. | -| `offset` | `tuple[float, float, float]` | Site position in the link's local frame; contributes the lever-arm term to linear acceleration. | -| `gravity_bias` | `bool` | When `True`, output is the specific force `R^T (a_w - g_w)` — matches a real accelerometer at rest reading `+g` along its up axis. | -| `bias_range_lin_acc` | `tuple[float, float] \| None` | Per-env constant accelerometer bias resampled on `reset`. | -| `bias_range_ang_acc` | `tuple[float, float] \| None` | Per-env constant gyro-derivative bias resampled on `reset`. | - -`IMUSensor` does not output linear or angular velocity — that remains the role of -`BodyVelocitySensor`, which can coexist on the same link when both signals are needed. The -finite-difference at the control rate is **not** filtered internally; layer -`ObservationTermCfg.noise` + `scale` on top, or apply a custom EMA in the observation term. - -### CameraSensor - -Rigid-mount RGB-D camera bolted to a named link via a fixed 4×4 transform. -`pre_build_genesis()` runs before `gs_scene.build` so the camera is registered in -time for `BatchRenderer` to snapshot it; it allocates the Genesis camera handle -through `gs_scene.add_camera`, builds the offset matrix from `offset_pos` / -`offset_quat`, and calls `cam.attach`. `bind()` then validates the link name -against `env.link_names`. Each `data` access invokes `cam.move_to_attach()` and -`cam.render(...)` once per control step; the cached `CameraData` returns `rgb` -(uint8) and / or `depth` (float meters), shaped `(num_envs, H, W, 3)` / -`(num_envs, H, W)`. - -| Field | Type | Meaning | -|-------|------|---------| -| `link_name` | `str` | Link the camera is bolted to (resolved through `env.link_names`). | -| `offset_pos` | `tuple[float, float, float]` | Camera origin in the link's local frame. | -| `offset_quat` | `tuple[float, float, float, float]` | Camera orientation (wxyz) relative to the link; `+x` is forward in Genesis convention. | -| `width`, `height` | `int` | Pixel resolution; all BatchRender cameras on a scene must share resolution. | -| `fov` | `float` | Vertical field-of-view in degrees. | -| `near`, `far` | `float` | Depth clip planes in meters. | -| `render_rgb`, `render_depth` | `bool` | Independently toggle each channel; the disabled channel is `None` in the output dataclass. | - -!!! warning "BatchRenderer is the only parallel-env backend" - Multi-env RGB-D requires `gs.init(backend=gs.cuda)` and - `InteractiveSceneCfg.batch_render=True` — the scene then passes - `gs.renderers.BatchRenderer(use_rasterizer=False)` to `gs.Scene`. Linux x86-64 + - CUDA only. The module file can still be imported on macOS / CPU, but - `gs_scene.build` will raise once it inspects the camera list against an - incompatible renderer. - -### ContactSensor - -Per-link aggregate of `robot.get_links_net_contact_force()`. With `track_air_time=True`, an -internal state machine advances `current_air_time` / `current_contact_time` per env, and snaps -the completed durations into `last_air_time` / `last_contact_time` at the contact transition. - -| Field | Type | Meaning | -|-------|------|---------| -| `link_names` | `tuple[str, ...]` | Explicit list of link names to monitor. | -| `link_names_expr` | `str \| None` | Regex matched against `env.link_names`. | -| `force_threshold` | `float` | Force magnitude (N) above which `found` is true. | -| `track_air_time` | `bool` | Allocate the air-time / contact-time state machine. | - -`data` is a `ContactData` dataclass with `force`, `force_norm`, `found`, plus the four air-time -buffers. The matching obs terms — `mdp.foot_air_time`, `mdp.foot_contact`, -`mdp.foot_contact_forces` — read straight off this dataclass. - -### FrameTransformerSensor - -Stateless forward-kinematics probe: outputs the pose of one or more target frames relative -to a single source frame, with rigid offsets in each link's local frame. Both the world-frame -pose and the source-frame pose are exposed in the data class so reward / observation terms -can pick either basis without re-running the math themselves. Typical uses include reading -an end-effector's pose in the base link's frame, comparing two foot-tip frames during gait -shaping, or expressing a payload's pose relative to a grasp site. - -| Field | Type | Meaning | -|-------|------|---------| -| `source_link_name` | `str` | Reference frame the targets are expressed in. | -| `source_offset_pos` | `tuple[float, float, float]` | Source-side offset in the link's local frame. | -| `source_offset_quat` | `tuple[float, float, float, float]` | Source-side rotation offset (wxyz). | -| `target_frames` | `tuple[TargetFrameCfg, ...]` | Order-preserving target list — the output's `N` axis follows cfg order. | - -Each `TargetFrameCfg` carries `link_name`, optional `name` (defaults to `link_name`), -`offset_pos`, and `offset_quat`. Sensor attribute `target_names: tuple[str, ...]` is exposed -so consumers can resolve a column by name. `bind` rejects unknown link names and empty -`target_frames`. - -### Ray-cast patterns - -`RayCastSensorCfg.pattern` accepts any of the three bundled pattern dataclasses. Custom -patterns satisfy the same informal protocol — `num_rays() -> int` and -`generate(device) -> (starts, dirs)` with both tensors shaped `(M, 3)` in the sensor's local -frame — and slot in without changing `RayCastSensor` itself. - -`GridPattern` is the default; rays are parallel and arranged on a 2D rectangle. `RingPattern` -emits `num_horizontal × num_vertical` rays from the origin, evenly spaced in azimuth and -elevation — the typical multi-line LIDAR layout. `HemispherePattern` distributes -`num_rays_target` rays on a spherical cap of half-angle `polar_fov_deg` around `pole_axis` -using a Fibonacci lattice; 90° covers a full hemisphere, 180° a full sphere. - -| Pattern | Key fields | Use case | -|---------|------------|----------| -| `GridPattern` | `resolution`, `size`, `direction` | Height-scan grids, area sweeps | -| `RingPattern` | `num_horizontal`, `num_vertical`, `horizontal_fov_deg`, `vertical_fov_deg` | Planar / multi-line LIDAR | -| `HemispherePattern` | `num_rays_target`, `pole_axis`, `polar_fov_deg` | Proximity dome, downward coverage | - -`RingPattern` treats a horizontal span of exactly ±360° as a wrap-around and drops the -duplicate closing azimuth; any other span (e.g. `(-30, 30)` for a forward-facing scanner) is -inclusive on both endpoints. `HemispherePattern.num_rays()` returns `num_rays_target` -exactly — the Fibonacci lattice produces no rounding error. - -### TerrainHeightSensor - -2D grid of downward rays anchored to a robot link. Output is per-ray height above the terrain -(positive = above), useful as a privileged `height_scan` critic observation. The default -backend intersects every ray against a horizontal plane at `ground_height`; when the scene -attaches a `TerrainImporter`, the inner `RayCastSensor` bilinearly samples the height-field -instead. Subclassing `RayCastSensor` and overriding `_intersect_world_rays` is the extension -point for BVH or other custom backends. - -| Field | Type | Meaning | -|-------|------|---------| -| `link_name` | `str` | Anchor link for the grid origin. | -| `pattern` | `GridPattern \| RingPattern \| HemispherePattern` | Pattern geometry. | -| `attach_yaw_only` | `bool` | Rotate the pattern by yaw only so it stays horizon-aligned. | -| `max_distance` | `float` | Distance clamp for the ray cast. | -| `ground_height` | `float` | Plane height used by the default flat-plane backend. | - -## Observations with noise - -`ObservationTermCfg.noise` accepts an additive noise model (`Unoise(n_min, n_max)` or -`Gnoise(mean, std)`). `ObservationGroupCfg.enable_corruption` gates whether the noise is -applied — disabled by default to keep the critic on ground truth. The per-term pipeline order -is **noise → scale → clip**, so noise magnitudes live in raw signal space: `Unoise(-1.5, 1.5)` -on a raw `joint_vel` term with `scale=0.05` ends up as ±0.075 final jitter. - -The canonical pattern shares terms between policy and critic and differs only on -`enable_corruption`: - -```python -from genelab import mdp -from genelab.managers import ObservationGroupCfg, ObservationTermCfg -from genelab.mdp.noise import Unoise - - -def _obs_terms() -> dict[str, ObservationTermCfg]: - return { - "base_lin_vel": ObservationTermCfg( - func=mdp.sensor_data, - params={"sensor_name": "imu_lin_vel"}, - noise=Unoise(-0.5, 0.5), - ), - "joint_vel": ObservationTermCfg( - func=mdp.joint_vel_rel, - scale=0.05, - noise=Unoise(-1.5, 1.5), - ), - } - - -policy = ObservationGroupCfg(enable_corruption=True, terms=_obs_terms()) -critic = ObservationGroupCfg(enable_corruption=False, terms=_obs_terms()) -``` - -## Writing a custom sensor - -Subclass `Sensor[T]` with the desired return type and implement `_compute_data`. Override -`bind` to cache link indices once, `update` to advance integrators, and `reset` to clear per-env -state — always calling `super()` first so the cache-invalidation chain stays intact. - -```python -from dataclasses import dataclass -from typing import TYPE_CHECKING - -import torch - -from genelab.sensor import Sensor, SensorCfg - -if TYPE_CHECKING: - from genelab.envs.manager_based_rl_env import ManagerBasedRlEnv - - -@dataclass -class JointTorqueSensorCfg(SensorCfg): - def build(self) -> "JointTorqueSensor": - return JointTorqueSensor(self) - - -class JointTorqueSensor(Sensor[torch.Tensor]): - def bind(self, env: "ManagerBasedRlEnv") -> None: - super().bind(env) - # Cache anything that depends on env.link_names / env.joint_names here. - - def _compute_data(self) -> torch.Tensor: - assert self._env is not None - rs = self._env.robot_state - return self._env.joint_kp * (rs.joint_pos - self._env.default_joint_pos) -``` - -Add the cfg to `InteractiveSceneCfg.sensors` and the sensor is reachable as -`env.sensors[name].data`. - -## See also - -- [Configs](configs.md) +- [Recording and plotting](recording.md) +- [Module Map](../reference/module-map.md) - [API Reference](../api/reference.md) diff --git a/docs/concepts/sensors.zh.md b/docs/concepts/sensors.zh.md index 1fa18590..b6eed6ba 100644 --- a/docs/concepts/sensors.zh.md +++ b/docs/concepts/sensors.zh.md @@ -1,265 +1,41 @@ # 传感器 -Genesis 不解析 MJCF 中的 `` 段,因此 GeneLab 提供了一套 backend-agnostic 的传感器抽象。 -接口与 mjlab 的 `SensorCfg` / `Sensor[T]` 对齐,每个具体传感器从 env 的 `RobotState` 而非 -MuJoCo sensordata 取数 —— 观测与奖励 term 可在两套后端之间无缝迁移。 +Genesis 不会通过 MJCF sensor block 暴露所有信号,因此 GeneLab 提供后端感知的传感器抽象。 +传感器产出带缓存的数据,供 observation、reward、recording 和自定义代码消费。 ## 生命周期 -`bind(env)` 在构造时调用一次。每个控制步 `update(dt)` 失效缓存;首次访问 `data` 触发 -`_compute_data` 惰性求值;`reset(env_ids)` 同样失效缓存,并允许有状态传感器清空对应 env 的 -缓冲区。 +传感器声明在 `InteractiveSceneCfg.sensors` 中。`ManagerBasedRlEnv` 在 scene 存在后构建并绑定它们。 +每个控制步会使缓存失效;第一次访问 `sensor.data` 时计算新输出。reset 会清理每 env 状态。 -```python -class Sensor[T](ABC): - def bind(self, env: "ManagerBasedRlEnv") -> None: ... - @property - def data(self) -> T: ... - def update(self, dt: float) -> None: ... - def reset(self, env_ids: torch.Tensor | None = None) -> None: ... - @abstractmethod - def _compute_data(self) -> T: ... -``` +这种惰性缓存保证同一个控制步内多次 observation 和 reward 读取一致。 -env 自动接好生命周期:`InteractiveSceneCfg.sensors` 里构造出的传感器,`update` 在每次 -articulation 刷新之后被调用,`reset` 在 `_reset_idx` 内部被调用 —— 奖励和观测 term 总能读 -到当前步的数据。 +## 内置传感器族 -## 注册到 scene +| 传感器 | 用途 | +|---|---| +| `BodyVelocitySensor` | offset 处 link 线速度或角速度。 | +| `IMUSensor` | 姿态、projected gravity、线/角加速度。 | +| `CameraSensor` | 挂到 link 上的 RGB-D 相机。 | +| `ContactSensor` | 每 link 接触力和可选 air/contact time。 | +| `FrameTransformerSensor` | target frame 相对 source frame 的姿态。 | +| `RayCastSensor` | grid、ring、hemisphere ray pattern。 | +| `TerrainHeightSensor` | 地形感知 locomotion 的向下 height scan。 | +| `SelfContactSensor` | 自碰撞/自接触信号。 | +| `RootAngularMomentumSensor` | root angular momentum 诊断。 | -`InteractiveSceneCfg.sensors` 是一个 `SensorCfg` 元组。`ManagerBasedRlEnv.__init__` 对每 -个 cfg 调用 `build()` 并把生成的传感器绑定到 env。运行时通过 `env.sensors[name].data` 访 -问。 +## MDP term 中的传感器数据 -```python -from genelab.configs import InteractiveSceneCfg, SimulationCfg -from genelab.sensor import BodyVelocitySensorCfg, ContactSensorCfg +observation term 可以通过 `mdp.sensor_data` 或自定义 callable 读取传感器。reward 和 metric 可以直接访问 `env.sensors[name].data`。 -simulation = SimulationCfg(num_envs=4096) -scene = InteractiveSceneCfg( - sensors=( - BodyVelocitySensorCfg( - name="imu_lin_vel", - link_name="pelvis", - offset=(0.04525, 0.0, -0.08339), - measure="lin_vel", - ), - ContactSensorCfg( - name="feet_ground_contact", - link_names=("left_ankle_roll_link", "right_ankle_roll_link"), - track_air_time=True, - ), - ), -) -``` +保持传感器名称稳定;它们会成为配置路径、recording source 和 term params。 -## 内置传感器 +## Camera 注意事项 -### BodyVelocitySensor +并行 RGB-D 渲染需要 CUDA 和 `InteractiveSceneCfg.batch_render=True`。把 camera task 当成不同于轻量状态 RL task 的硬件 profile。 -对应 MuJoCo 的 `` / ``:在与 link 刚性连接的 site 上读取线速度或角速度, -结果旋转到 link body frame。支持杠杆臂偏移与可选的每 env 均匀分布偏置。 +## 继续阅读 -| 字段 | 类型 | 含义 | -|------|------|------| -| `link_name` | `str` | site 刚性连接到的 link。 | -| `offset` | `tuple[float, float, float]` | site 在 link 局部系下的位置,`ang_vel` 模式忽略。 | -| `measure` | `Literal["lin_vel", "ang_vel"]` | 选择 velocimeter(线速度)或 gyro(角速度)。 | -| `bias_range` | `tuple[float, float] \| None` | 每 env 均匀偏置,`reset` 时重采样。 | - -velocimeter 计算公式为 `v_site = v_link + ω × (R_link · offset)`,再旋转到 body frame —— -与 MuJoCo 的杠杆臂约定一致,使得 GeneLab 训出的策略读到的信号与 mjlab 参考实现完全相同。 - -### IMUSensor - -刚性附着在某个 link 上的惯性测量单元,输出姿态(`link_quat_w`)、body frame 投影单位 -重力、以及 body frame 下的线 / 角加速度。加速度通过对世界系速度做有限差分得到;每次 -`reset` 之后的第一个控制步加速度恒为零,避免使用上一阶段残留的 `prev` 缓冲产生 -伪冲击 —— 接近 reset 边界的 reward / observation term 在消费 `lin_acc_b` 或 -`ang_acc_b` 时需要意识到这点。 - -| 字段 | 类型 | 含义 | -|------|------|------| -| `link_name` | `str` | IMU 刚性附着的 link。 | -| `offset` | `tuple[float, float, float]` | site 在 link 局部系下的位置;线加速度计算中携带杠杆臂项。 | -| `gravity_bias` | `bool` | 为 `True` 时输出比力 `R^T (a_w - g_w)`,与真实加速度计一致 —— 静止时读到 `+g` 沿向上轴。 | -| `bias_range_lin_acc` | `tuple[float, float] \| None` | 加速度计每 env 常数偏置,`reset` 时重采样。 | -| `bias_range_ang_acc` | `tuple[float, float] \| None` | 陀螺导出量每 env 常数偏置,`reset` 时重采样。 | - -`IMUSensor` 不输出线速度或角速度 —— 这是 `BodyVelocitySensor` 的职责,二者在同一 link -上可以并存。控制步频率下的有限差分内部不做滤波;如需平滑,可在 `ObservationTermCfg` -上叠加 `noise` + `scale`,或者在观测 term 里自行做 EMA。 - -### CameraSensor - -刚性挂载的 RGB-D 相机,通过 4×4 变换螺接到指定 link 上。`pre_build_genesis()` 在 -`gs_scene.build` 之前先跑,正好让相机赶在 `BatchRenderer` 快照之前注册——它调 -`gs_scene.add_camera` 拿一个 Genesis camera 句柄,根据 `offset_pos` / `offset_quat` -构造偏置矩阵后 `cam.attach`。`bind()` 之后再去 `env.link_names` 里做 link 名校验。每次 -访问 `data` 都会先 `cam.move_to_attach()` 再 `cam.render(...)`,缓存的 `CameraData` -携带 `rgb` (uint8) 与/或 `depth` (米,float),形状 `(num_envs, H, W, 3)` / -`(num_envs, H, W)`。 - -| 字段 | 类型 | 含义 | -|------|------|------| -| `link_name` | `str` | 相机挂载到的 link(通过 `env.link_names` 解析)。 | -| `offset_pos` | `tuple[float, float, float]` | 相机原点在 link 局部系下的位置。 | -| `offset_quat` | `tuple[float, float, float, float]` | 相机相对 link 的姿态(wxyz);Genesis 约定 `+x` 为前向。 | -| `width`、`height` | `int` | 像素分辨率;同一 scene 内所有 BatchRender 相机分辨率必须一致。 | -| `fov` | `float` | 垂直视场角(度)。 | -| `near`、`far` | `float` | 深度裁剪面(米)。 | -| `render_rgb`、`render_depth` | `bool` | 两条通道独立开关;关闭的那条在输出数据类里为 `None`。 | - -!!! warning "BatchRenderer 是唯一的多 env 后端" - 多 env RGB-D 需要 `gs.init(backend=gs.cuda)` 加 `InteractiveSceneCfg.batch_render=True` - —— scene 会把 `gs.renderers.BatchRenderer(use_rasterizer=False)` 传给 `gs.Scene`。 - 仅 Linux x86-64 + CUDA 可用。macOS / 纯 CPU 环境下模块本身可正常 import,但 - `gs_scene.build` 一开始按相机列表配 renderer 时就会抛错。 - -### ContactSensor - -按 link 名汇总 `robot.get_links_net_contact_force()` 输出。开启 `track_air_time=True` 时, -一个内部状态机会推进每 env 的 `current_air_time` / `current_contact_time`,并在接触状态翻转 -的瞬间把已完成的时长快照到 `last_air_time` / `last_contact_time`。 - -| 字段 | 类型 | 含义 | -|------|------|------| -| `link_names` | `tuple[str, ...]` | 显式列出需要监控的 link。 | -| `link_names_expr` | `str \| None` | 对 `env.link_names` 做正则匹配。 | -| `force_threshold` | `float` | 力幅值(N)大于此值时 `found` 为真。 | -| `track_air_time` | `bool` | 是否分配 air-time / contact-time 状态机。 | - -`data` 是 `ContactData` 数据类,包含 `force`、`force_norm`、`found` 以及四个 air-time 缓冲。 -配套的观测 term —— `mdp.foot_air_time`、`mdp.foot_contact`、`mdp.foot_contact_forces` —— -直接读取这个数据类。 - -### FrameTransformerSensor - -无状态的正向运动学探针:输出一个或多个目标系相对于单一源系的位姿,源 / 目标在各自 -link 局部系下都支持刚性偏移。数据类同时暴露世界系位姿与源系位姿,下游 reward / -observation term 可按需取用而不必自行复合运算。典型场景:在 base link 系下读取末端执行器 -位姿、对比两个脚尖系做步态约束、把负载位姿表达在抓取点局部系下。 - -| 字段 | 类型 | 含义 | -|------|------|------| -| `source_link_name` | `str` | 表达 target 所用的参考系。 | -| `source_offset_pos` | `tuple[float, float, float]` | 源端在 link 局部系下的位移偏移。 | -| `source_offset_quat` | `tuple[float, float, float, float]` | 源端旋转偏移(wxyz)。 | -| `target_frames` | `tuple[TargetFrameCfg, ...]` | 保序的 target 列表 —— 输出 `N` 轴顺序即 cfg 顺序。 | - -每个 `TargetFrameCfg` 含 `link_name`、可选的 `name`(默认为 `link_name`)、`offset_pos` -与 `offset_quat`。传感器属性 `target_names: tuple[str, ...]` 暴露给下游按名字定位列。 -`bind` 会拒绝未知 link 名以及空 `target_frames`。 - -### Ray-cast patterns - -`RayCastSensorCfg.pattern` 接受三种内置 pattern 数据类。自定义 pattern 满足同一非正式协议 -即可接入 —— `num_rays() -> int` 和 `generate(device) -> (starts, dirs)`,两个张量都为 -`(M, 3)`,定义在传感器局部系下 —— 无须改动 `RayCastSensor` 本身。 - -`GridPattern` 是默认:所有光线平行,原点排在 2D 矩形上。`RingPattern` 从原点发出 -`num_horizontal × num_vertical` 条光线,方位角和俯仰角各自均匀分布 —— 典型的多线 LIDAR -布局。`HemispherePattern` 用 Fibonacci 格点把 `num_rays_target` 条光线均匀分布到以 -`pole_axis` 为极轴、半角为 `polar_fov_deg` 的球冠上;90° 覆盖整个半球,180° 即整个球面。 - -| Pattern | 关键字段 | 典型用途 | -|---------|----------|----------| -| `GridPattern` | `resolution`、`size`、`direction` | 高度场网格、面扫 | -| `RingPattern` | `num_horizontal`、`num_vertical`、`horizontal_fov_deg`、`vertical_fov_deg` | 平面 / 多线 LIDAR | -| `HemispherePattern` | `num_rays_target`、`pole_axis`、`polar_fov_deg` | 接近告警球罩、向下覆盖 | - -`RingPattern` 将水平角恰好 ±360° 视为环绕扫描,自动丢弃首末重复方位;任意其它跨度(例如 -`(-30, 30)` 的前向扫描器)则把两端点都包含进去。`HemispherePattern.num_rays()` 严格返回 -`num_rays_target`——Fibonacci 格点不会产生取整误差。 - -### TerrainHeightSensor - -锚定在某个 link 上的 2D 下射光线网格,输出每条光线相对地形的高度(正值表示在地形上方), -适合作为 critic 的 privileged `height_scan` 观测。默认 backend 把每条光线打到位于 -`ground_height` 的水平面上;当场景挂上 `TerrainImporter` 时,内部 `RayCastSensor` 会 -对 height-field 做双线性采样。非平地场景的扩展点是继承 `RayCastSensor` 并重载 -`_intersect_world_rays`,可换成 BVH 或其他自定义 backend。 - -| 字段 | 类型 | 含义 | -|------|------|------| -| `link_name` | `str` | 网格原点锚定的 link。 | -| `pattern` | `GridPattern \| RingPattern \| HemispherePattern` | Pattern 几何。 | -| `attach_yaw_only` | `bool` | 仅按 yaw 旋转 pattern,使其保持水平。 | -| `max_distance` | `float` | 光线距离上限。 | -| `ground_height` | `float` | 默认平面 backend 使用的地面高度。 | - -## 给观测加噪 - -`ObservationTermCfg.noise` 接收加性噪声模型(`Unoise(n_min, n_max)` 或 -`Gnoise(mean, std)`)。`ObservationGroupCfg.enable_corruption` 控制是否真正加噪 —— 默认关闭, -让 critic 看到 ground truth。逐 term 的管线顺序是 **noise → scale → clip**,因此噪声幅值 -定义在原始信号空间:在带 `scale=0.05` 的原始 `joint_vel` 上加 `Unoise(-1.5, 1.5)`,最终的 -抖动量为 ±0.075。 - -惯例做法是 policy / critic 共享 term,只在 `enable_corruption` 上分叉: - -```python -from genelab import mdp -from genelab.managers import ObservationGroupCfg, ObservationTermCfg -from genelab.mdp.noise import Unoise - - -def _obs_terms() -> dict[str, ObservationTermCfg]: - return { - "base_lin_vel": ObservationTermCfg( - func=mdp.sensor_data, - params={"sensor_name": "imu_lin_vel"}, - noise=Unoise(-0.5, 0.5), - ), - "joint_vel": ObservationTermCfg( - func=mdp.joint_vel_rel, - scale=0.05, - noise=Unoise(-1.5, 1.5), - ), - } - - -policy = ObservationGroupCfg(enable_corruption=True, terms=_obs_terms()) -critic = ObservationGroupCfg(enable_corruption=False, terms=_obs_terms()) -``` - -## 自定义传感器 - -继承 `Sensor[T]` 并指定返回类型,实现 `_compute_data`。如需缓存 link 索引、推进积分器或 -清空 env 状态,重载 `bind` / `update` / `reset` 并先调用 `super()`,让缓存失效链路保持完整。 - -```python -from dataclasses import dataclass -from typing import TYPE_CHECKING - -import torch - -from genelab.sensor import Sensor, SensorCfg - -if TYPE_CHECKING: - from genelab.envs.manager_based_rl_env import ManagerBasedRlEnv - - -@dataclass -class JointTorqueSensorCfg(SensorCfg): - def build(self) -> "JointTorqueSensor": - return JointTorqueSensor(self) - - -class JointTorqueSensor(Sensor[torch.Tensor]): - def bind(self, env: "ManagerBasedRlEnv") -> None: - super().bind(env) - # 在这里缓存依赖 env.link_names / env.joint_names 的索引。 - - def _compute_data(self) -> torch.Tensor: - assert self._env is not None - rs = self._env.robot_state - return self._env.joint_kp * (rs.joint_pos - self._env.default_joint_pos) -``` - -把 cfg 加入 `InteractiveSceneCfg.sensors` 后,传感器即可通过 `env.sensors[name].data` 访问。 - -## See also - -- [配置系统](configs.md) +- [数据录制与实时绘图](recording.md) +- [模块地图](../reference/module-map.md) - [API 参考](../api/reference.md) diff --git a/docs/concepts/terrains.en.md b/docs/concepts/terrains.en.md index d0f8981f..223e9876 100644 --- a/docs/concepts/terrains.en.md +++ b/docs/concepts/terrains.en.md @@ -1,128 +1,36 @@ # Terrains -`genelab.terrains` is a thin GeneLab-shaped wrapper over Genesis's native -`gs.morphs.Terrain` morph. Sub-terrain configs map onto Genesis's built-in subterrain -string types, `TerrainGenerator` assembles them into a 2D grid, and `TerrainImporter` -spawns the terrain into the scene plus tracks per-env curriculum state. +GeneLab terrains are generated height-field grids that can be imported into Genesis scenes and read +by ray-based sensors and curricula. -## Why a dedicated layer +## Why a terrain layer -Genesis ships the height-field terrain primitives, but its native API uses pydantic -options keyed by string names (`"pyramid_stairs_terrain"`, `"random_uniform_terrain"`). -A GeneLab cfg layer keeps the surface dataclass-shaped (matching the rest of the project), -gives every sub-terrain a typed Python class with explicit fields, and tracks per-env -spawn / level state so curriculum terms can promote and demote without touching the -Genesis handle directly. +Locomotion tasks need terrain geometry, spawn origins, difficulty levels, and height scans to agree. +Keeping terrain generation in one layer lets the scene, curriculum, and sensors share the same +source of truth. -## Five shipped sub-terrains +## Building blocks -| Class | Genesis type | Parameters | -|---|---|---| -| `FlatPatchCfg` | `flat_terrain` | none | -| `PyramidStairsCfg` | `pyramid_stairs_terrain` | `step_width`, `step_height` | -| `RandomRoughCfg` | `random_uniform_terrain` | `min_height`, `max_height`, `step`, `downsampled_scale` | -| `SlopeCfg` | `sloped_terrain` | `slope` | -| `WaveCfg` | `wave_terrain` | `num_waves`, `amplitude` | +| Config | Shape | +|---|---| +| `FlatPatchCfg` | Flat sub-terrain. | +| `PyramidStairsCfg` | Stair pattern. | +| `RandomRoughCfg` | Random rough surface. | +| `SlopeCfg` | Sloped patch. | +| `WaveCfg` | Wave-like height field. | +| `TerrainGeneratorCfg` | Grid layout and selected sub-terrains. | +| `TerrainImporter` | Runtime bridge into Genesis. | -`PyramidStairsCfg(step_height=-0.1)` builds concentric descending steps; positive -`step_height` would invert the pyramid. `RandomRoughCfg` samples bumps at -`downsampled_scale` then upsamples, so the apparent feature size is independent of -`horizontal_scale`. `SlopeCfg(slope=-0.5)` tilts the patch uniformly (negative follows -Genesis's default downhill direction); `WaveCfg` lays sinusoidal undulations across -the patch — a useful intermediate difficulty between flat and pyramid-stairs. Four -more Genesis types (`pyramid_sloped`, `discrete_obstacles`, `stairs`, `stepping_stones`) -are not wrapped yet — extending `SubTerrainCfg` to expose them is a 20-line task. +## Integration points -## Composing a terrain grid +| Consumer | Uses terrain for | +|---|---| +| `InteractiveScene` | Adds generated geometry to the Genesis scene. | +| `TerrainHeightSensor` | Samples heights under a ray grid. | +| `terrain_levels_vel` curriculum | Promotes/demotes env difficulty by performance. | -```python -from genelab.terrains import ( - FlatPatchCfg, - PyramidStairsCfg, - RandomRoughCfg, - TerrainGeneratorCfg, -) -from genelab.configs import InteractiveSceneCfg +## Where to continue -scene = InteractiveSceneCfg( - env_spacing=(0.0, 0.0), - terrain=TerrainGeneratorCfg( - num_rows=4, - num_cols=4, - subterrain_size=(8.0, 8.0), - horizontal_scale=0.1, - vertical_scale=0.005, - sub_terrains={ - "flat": FlatPatchCfg(proportion=1.0), - "stairs": PyramidStairsCfg(step_width=0.5, step_height=-0.08, proportion=2.0), - "rough": RandomRoughCfg(min_height=-0.05, max_height=0.05, proportion=1.0), - }, - curriculum=True, - seed=0, - ), -) -``` - -`sub_terrains` is a `dict[str, SubTerrainCfg]`; the key is the local cfg label, the -value carries the Genesis parameters. `layout` defaults to `None` (random tiling by -`proportion`); pass a `tuple[tuple[str, ...], ...]` of cfg keys to pin the grid -deterministically. - -!!! warning "One parameter set per Genesis type" - Genesis keys `subterrain_parameters` by the *type string*, not per cell. Two - `PyramidStairsCfg` instances with different `step_width` collapse to a single - parameter set. Use distinct keys for distinct geometries, or rely on - `randomize=True` on the importer for in-type variation. - -## Ray-cast sensor integration - -When `InteractiveSceneCfg.terrain` is set, `InteractiveScene.build()` spawns -`gs.morphs.Terrain` instead of the default plane and exposes the importer as -`scene.terrain`. `RayCastSensor` checks for it and, when present, bilinearly samples -`terrain.heightfield_tensor` at each ray's world `(x, y)` to compute hit elevation. -`TerrainHeightSensor` is a thin wrapper around the same path, so the per-link height -scan output reflects the true terrain instead of a constant plane. - -Vertical and near-vertical rays match the underlying height field to within -`vertical_scale`. Oblique rays use the elevation at the ray-start `(x, y)` — accurate -for height-scan grids but approximate for BVH-style queries; override -`RayCastSensor._intersect_world_rays` for those. - -## Curriculum: `terrain_levels_vel` - -```python -from genelab.managers import CurriculumTermCfg -from genelab.mdp import terrain_levels_vel - -curriculum_cfg = { - "terrain_levels": CurriculumTermCfg( - func=terrain_levels_vel, - params={"distance_threshold": 2.0, "demote_ratio": 0.5}, - ), -} -``` - -Each reset, the term compares every env's walked distance (XY norm of `root_pos - -spawn_pos`) against `distance_threshold`. Envs that walked further than the threshold -move up one row of the difficulty grid (capped at `num_rows - 1`); envs that walked -less than `distance_threshold * demote_ratio` move down (floored at 0). The curriculum -then writes the new spawn pose into the sim via `Articulation.write_root_state`, so -the next episode starts on the new sub-terrain. The manager logs the mean level under -`Curriculum/terrain_levels`. - -## Failure modes worth knowing - -* **Empty `sub_terrains`** — `TerrainGenerator.__init__` raises immediately; the - generator has nothing to tile. -* **`layout` shape mismatch** — `ValueError` lists the actual vs declared shape. -* **`layout` references unknown key** — `ValueError` lists the missing keys. -* **Per-env state access before build** — `terrain_levels` / `terrain_cols` / - `spawn_pos` raise `RuntimeError` until `init_per_env_state` runs; - `InteractiveScene.build` calls it automatically. -* **`heightfield` access before spawn** — `RuntimeError`; spawn first, then read. - -## See also - -- [Configs](configs.md) - [Sensors](sensors.md) -- [Actuators](actuators.md) +- [Managers and MDP terms](managers.md) +- [API Reference](../api/reference.md) diff --git a/docs/concepts/terrains.zh.md b/docs/concepts/terrains.zh.md index 1a83d125..832566a3 100644 --- a/docs/concepts/terrains.zh.md +++ b/docs/concepts/terrains.zh.md @@ -1,90 +1,33 @@ # 地形 -`genelab.terrains` 是 Genesis 原生 `gs.morphs.Terrain` morph 之上的一层 GeneLab 风格薄包装。子地形 cfg 对应 Genesis 内建的 subterrain 字符串类型,`TerrainGenerator` 把它们拼成一个二维网格,`TerrainImporter` 负责把地形 spawn 进场景并跟踪每个 env 的课程状态。 +GeneLab 地形是生成式 height-field 网格,可导入 Genesis scene,并被 ray-based sensor 和 curriculum 读取。 -## 为什么需要专门的一层 +## 为什么需要地形层 -Genesis 自带 height-field 地形原语,但其原生 API 使用按字符串名键控的 pydantic options(`"pyramid_stairs_terrain"`、`"random_uniform_terrain"`)。GeneLab 的 cfg 层把接口保持成 dataclass 形态(与项目其它部分一致),让每种子地形拥有带显式字段的 typed Python 类,并跟踪每个 env 的 spawn / level 状态,让课程项可以提升和降级而不直接接触 Genesis 句柄。 +locomotion 任务需要地形几何、spawn origin、难度等级和 height scan 保持一致。把地形生成放在一层里,可以让 scene、curriculum 和 sensor 共享同一事实来源。 -## 自带 5 种子地形 +## 构件 -| 类 | Genesis 类型 | 参数 | -|---|---|---| -| `FlatPatchCfg` | `flat_terrain` | 无 | -| `PyramidStairsCfg` | `pyramid_stairs_terrain` | `step_width`、`step_height` | -| `RandomRoughCfg` | `random_uniform_terrain` | `min_height`、`max_height`、`step`、`downsampled_scale` | -| `SlopeCfg` | `sloped_terrain` | `slope` | -| `WaveCfg` | `wave_terrain` | `num_waves`、`amplitude` | +| 配置 | 形态 | +|---|---| +| `FlatPatchCfg` | 平坦子地形。 | +| `PyramidStairsCfg` | 楼梯 pattern。 | +| `RandomRoughCfg` | 随机粗糙表面。 | +| `SlopeCfg` | 斜坡 patch。 | +| `WaveCfg` | 波浪状 height field。 | +| `TerrainGeneratorCfg` | 网格布局与子地形选择。 | +| `TerrainImporter` | 到 Genesis 的运行时桥接。 | -`PyramidStairsCfg(step_height=-0.1)` 生成一圈圈向下的同心方形台阶;正的 `step_height` 会反转金字塔方向。`RandomRoughCfg` 在 `downsampled_scale` 下采样后再上采样,所以表观特征尺寸与 `horizontal_scale` 无关。`SlopeCfg(slope=-0.5)` 把整块斜成一个固定坡(负号沿 Genesis 默认的下坡方向);`WaveCfg` 在整块上铺正弦起伏 —— 介于平地与金字塔台阶之间的中间难度。Genesis 还有另外 4 种类型(`pyramid_sloped`、`discrete_obstacles`、`stairs`、`stepping_stones`)尚未包装 —— 扩展 `SubTerrainCfg` 暴露它们是一个 20 行的小活。 +## 接入点 -## 拼装地形网格 +| 消费者 | 用途 | +|---|---| +| `InteractiveScene` | 把生成几何加入 Genesis scene。 | +| `TerrainHeightSensor` | 在 ray grid 下采样高度。 | +| `terrain_levels_vel` curriculum | 按表现提升/降低 env 难度。 | -```python -from genelab.terrains import ( - FlatPatchCfg, - PyramidStairsCfg, - RandomRoughCfg, - TerrainGeneratorCfg, -) -from genelab.configs import InteractiveSceneCfg +## 继续阅读 -scene = InteractiveSceneCfg( - env_spacing=(0.0, 0.0), - terrain=TerrainGeneratorCfg( - num_rows=4, - num_cols=4, - subterrain_size=(8.0, 8.0), - horizontal_scale=0.1, - vertical_scale=0.005, - sub_terrains={ - "flat": FlatPatchCfg(proportion=1.0), - "stairs": PyramidStairsCfg(step_width=0.5, step_height=-0.08, proportion=2.0), - "rough": RandomRoughCfg(min_height=-0.05, max_height=0.05, proportion=1.0), - }, - curriculum=True, - seed=0, - ), -) -``` - -`sub_terrains` 是 `dict[str, SubTerrainCfg]`;key 是本地 cfg 标签,value 携带 Genesis 参数。`layout` 默认为 `None`(按 `proportion` 随机平铺);传入 `tuple[tuple[str, ...], ...]` 形态的 cfg key 二维表则可以确定性地固定网格。 - -!!! warning "每个 Genesis 类型只有一套参数" - Genesis 的 `subterrain_parameters` 按*类型字符串*键控,而不是按单元格。两个 `step_width` 不同的 `PyramidStairsCfg` 实例会塌成单一一套参数。要区分几何就用不同的 key,要做同类型内变化就在 importer 上启用 `randomize=True`。 - -## ray-cast 传感器接入 - -当 `InteractiveSceneCfg.terrain` 被设置时,`InteractiveScene.build()` 会 spawn `gs.morphs.Terrain` 而不是默认 plane,并把 importer 暴露为 `scene.terrain`。`RayCastSensor` 检测到地形后,会对每条射线在世界坐标 `(x, y)` 处对 `terrain.heightfield_tensor` 做双线性采样,得到命中点高度。`TerrainHeightSensor` 是同一路径上的薄封装,所以每个关节的 height scan 输出反映的是真实地形而不是常数平面。 - -垂直 / 接近垂直的射线,输出与底层 height field 的误差不超过 `vertical_scale`。斜射线使用射线起点 `(x, y)` 处的高度 —— 对 height-scan 网格足够准,但对 BVH 风格查询是近似的;这类场景请覆写 `RayCastSensor._intersect_world_rays`。 - -## 课程项: `terrain_levels_vel` - -```python -from genelab.managers import CurriculumTermCfg -from genelab.mdp import terrain_levels_vel - -curriculum_cfg = { - "terrain_levels": CurriculumTermCfg( - func=terrain_levels_vel, - params={"distance_threshold": 2.0, "demote_ratio": 0.5}, - ), -} -``` - -每次 reset,该项把每个 env 的已走距离(`root_pos - spawn_pos` 在 XY 上的范数)与 `distance_threshold` 比较。走得超过阈值的 env 在难度网格中上移一行(封顶在 `num_rows - 1`);走得不到 `distance_threshold * demote_ratio` 的 env 下移一行(地板在 0)。课程项随后通过 `Articulation.write_root_state` 把新 spawn 位姿写入仿真,下一个 episode 就从新子地形开始。manager 自动以 `Curriculum/terrain_levels` 记录平均 level。 - -## 需要知道的失败模式 - -* **`sub_terrains` 为空** —— `TerrainGenerator.__init__` 立刻抛错;生成器没东西可拼。 -* **`layout` 形状不匹配** —— `ValueError` 列出实际形状与声明形状。 -* **`layout` 引用未知 key** —— `ValueError` 列出缺失的 key。 -* **build 前访问 per-env 状态** —— `terrain_levels` / `terrain_cols` / `spawn_pos` 在 `init_per_env_state` 之前抛 `RuntimeError`;`InteractiveScene.build` 会自动调一次。 -* **spawn 前访问 `heightfield`** —— `RuntimeError`;先 spawn 再读。 - -## See also - -- [Configs](configs.md) -- [Sensors](sensors.md) -- [Actuators](actuators.md) +- [传感器](sensors.md) +- [Manager 与 MDP term](managers.md) +- [API 参考](../api/reference.md) diff --git a/docs/contributing.en.md b/docs/contributing.en.md index b28faeaf..ca993754 100644 --- a/docs/contributing.en.md +++ b/docs/contributing.en.md @@ -1,11 +1,48 @@ # Contributing -The canonical contributor guide lives in [`CONTRIBUTING.md`](https://github.com/KraHsu/GeneLab/blob/main/CONTRIBUTING.md) -at the repository root and is mirrored below. +This page is for contributors changing GeneLab itself. Downstream robot projects should usually use +an extension package instead. ---- +## Development setup -{% - include-markdown "../CONTRIBUTING.md" - heading-offset=1 -%} +```bash +uv sync --extra torch-cpu +uv run genelab cache +uv run pytest +uv run ruff check +uv run pyright +``` + +Pick one CUDA extra instead of `torch-cpu` when developing GPU workflows. + +## Documentation rules + +The docs follow Diátaxis: + +| Type | Purpose | +|---|---| +| Tutorial | Learning path from zero to a working result. | +| How-to | Task-focused instructions for experienced users. | +| Reference | Facts, flags, APIs, and defaults. | +| Explanation | Architecture and design rationale. | + +Keep pages in one type where possible and cross-link rather than mixing long conceptual digressions +into task guides. + +## Code style + +- Use Python 3.12+ syntax. +- Keep registry-time imports light; avoid starting Genesis at import time. +- Prefer dataclass configs for user-visible knobs. +- Keep task/example code outside `src/genelab/` unless it is part of the framework. + +## Before a PR + +```bash +uv run pytest +uv run ruff check +uv run pyright +uv run mkdocs build --strict +``` + +For docs-only changes, still run the MkDocs strict build. diff --git a/docs/contributing.zh.md b/docs/contributing.zh.md index d09e5d24..ce0419d6 100644 --- a/docs/contributing.zh.md +++ b/docs/contributing.zh.md @@ -1,94 +1,46 @@ # 贡献指南 -详细的贡献者指南以英文为权威版本,见仓库根目录 -[`CONTRIBUTING.md`](https://github.com/KraHsu/GeneLab/blob/main/CONTRIBUTING.md)。 +这页面向修改 GeneLab 本身的贡献者。下游机器人项目通常应使用扩展包。 ## 开发环境 -GeneLab 使用 [uv](https://github.com/astral-sh/uv) 并要求 Python `>=3.12`。挑选一个互斥的 -`torch-*` extra: - ```bash -uv sync --extra torch-cpu # 或 torch-cu126 / torch-cu128 / torch-cu130 -uv run genelab --help # CLI 烟雾测试 -uv run genelab cache # 创建 Genesis / Matplotlib 用的项目本地 .cache 目录 +uv sync --extra torch-cpu +uv run genelab cache +uv run pytest +uv run ruff check +uv run pyright ``` -`examples/genelab_examples/` 已加入 `pytest` 的 `pythonpath`,测试可直接 import 而无需安装。 +开发 GPU 工作流时,用一个 CUDA extra 替代 `torch-cpu`。 -## Python 3.12+ 风格 +## 文档规则 -GeneLab 锁定 Python `>=3.12`。只写现代代码,不要为旧解释器加兼容垫片: +文档遵循 Diátaxis: -- **不要** `from __future__ import annotations`。注解会立即被求值;只在 `TYPE_CHECKING` 下导入的 - 名字在使用点引号化(`def f(env: "ManagerBasedRlEnv") -> ...`)。 -- **PEP 604 union**:`X | Y`、`X | None`,不要 `Optional[X]` / `Union[X, Y]`。 -- **PEP 585 内置泛型**:`list[T]`、`dict[K, V]`、`tuple[T, ...]`、`type[T]`,不要 `typing.List` 等。 -- **PEP 695 泛型**:`class Foo[T]:`、`def f[T](...)`、`type Alias = ...`,不要 `TypeVar + Generic[T]`。 -- 用 `collections.abc` 代替 `typing` 中的 `Callable` / `Iterable` / `Iterator` / `Sequence` / `Mapping`。 -- 仅以下 `typing` 名字预期出现:`Any`、`cast`、`Protocol`、`Literal`、`Final`、`Annotated`、 - `TYPE_CHECKING`、`runtime_checkable`、`get_args`、`get_origin`、`get_type_hints`。其他名字 - 视为可疑。 +| 类型 | 用途 | +|---|---| +| Tutorial | 从零到可运行结果的学习路径。 | +| How-to | 面向有经验用户的任务指南。 | +| Reference | 事实、flag、API、默认值。 | +| Explanation | 架构和设计原因。 | -## 文档约定 +尽量让单页只承担一种文档类型,通过交叉链接连接,而不是把长篇概念解释塞进任务指南。 -文档位于 `docs/`,由 MkDocs Material 与 `mkdocs-static-i18n` 插件构建。每个内容页都有 -`.en.md` / `.zh.md` 双语版本;站点在 `/` 提供英文、`/zh/` 提供中文。下列规则同时适用于两种 -语言: +## 代码风格 -- **双语平行强制要求。** `.en.md` 修改时,同次提交需修改对应的 `.zh.md`。两文件需在标题数与 - 顺序、代码块(变量占位符除外)、表格结构、admonition、`See also` 条目上保持一致。 -- **章节标题用名词或动名词短语**,不要祈使语气。例如 `Scaffold output` / `生成的目录结构`、 - `Running a task` / `运行任务` —— 而不是 `Play a task` / `运行一个任务`。结尾块统一叫 - `## See also`,不要 `## Next steps`。 -- **不要写"本页介绍……"式开场白。** 避免在第一段铺垫"本页覆盖……"、`This walks through…`、 - `In this guide we'll…`。直接进入实质内容。 -- **避免第二人称。** 去掉 `you` / `你` / `您`。改用省略主语(操作命令用祈使语气)或名词短语 - (`The CLI exposes…` / `CLI 暴露…`)。 -- **正文不要插中段跨页跳转链接。** 不在正文段落里写 `see [Foo](...)` / `详见 [Foo](...)`。 - 把相关页面收在结尾的 `## See also` 块里,**≤ 3 条**,只放补充阅读(必要的下一步页面由 - 左侧导航承担)。 -- **callout 用 admonition,不用 blockquote。** `!!! warning "Title"` / `!!! tip "Title"` / - `!!! note "Title"`。`>` 只用于真正的引文。 -- **稳定显式锚点。** 含非 ASCII 字符、数字前缀或可能改名的标题,追加 `{ #stable-id }`。例如: - `## 5. Advanced: end-to-end RL on Unitree G1 { #unitree-g1 }`。跨链时使用该 slug,不要使用自动 - 生成的 slug。 -- **`.zh.md` 中的中英混排空格。** 中文字符与相邻的 ASCII 单词、数字或行内代码之间留一个空格 - (`运行 \`uv sync\``,不是 `运行\`uv sync\``)。 -- **`mkdocs build --strict` 必须通过。** 提交涉及文档的 PR 前运行 - `uv run mkdocs build --strict`——mkdocs 默认随 `uv sync` 安装。strict 模式会因任何未解析的 - 相对链接或锚点失败。 +- 使用 Python 3.12+ 语法。 +- 保持注册阶段导入轻量;不要在 import 时启动 Genesis。 +- 用户可见旋钮优先使用 dataclass 配置。 +- 除非属于框架本身,否则 task/example 代码不要放进 `src/genelab/`。 -## PR 前检查 +## PR 前 ```bash -uv run ruff check # lint -uv run ruff format --check # 格式(用 `uv run ruff format` 自动修复) -uv run pyright # src/genelab 的 strict 类型检查 -uv run pytest # 完整测试套件 +uv run pytest +uv run ruff check +uv run pyright +uv run mkdocs build --strict ``` -四项均须通过 —— CI 把同一组检查设为每个 PR 的必需 status checks。 - -## 分支与 PR 流程 - -GeneLab 采用两层流程:feature 分支先汇入 `dev`,再由 `dev` 通过 PR 提升到 `main`。`main` -受保护 —— 直接推送会被拒绝,必需的 CI 检查(`lint`、`typecheck`、`test`)必须全绿后才能 -合入 PR。 - -``` -feat/* ──┐ -fix/* ──┼──> dev ──PR──> main -docs/* ──┘ -``` - -1. 从 `dev` 派生分支,使用描述性前缀:`fix/...`、`feat/...`、`ci/...`、`chore/...`、`docs/...`。 -2. 本地跑上述检查。 -3. 推送后选择开 PR 合入 `dev`,或直接本地合入 `dev` 后推送 `dev` —— 视改动大小而定。 -4. 把 `dev` 提升到 `main` 时,开 `dev` → `main` 的 PR。CI 在每次 push 时自动运行,全绿后才能合入。 -5. 评审期间若 `dev` 前进,把 `dev` 合入分支(或 rebase),让 PR 保持最新。 - -## 提交信息 - -- 祈使语气、首字母大写:"Bump…"、"Fix…"、"Add…"。不要前缀、不要 emoji。 -- 主题行控制在约 72 字符内。正文解释 *why* —— *what* 已经被 diff 表达。 +docs-only 改动也要跑 MkDocs strict 构建。 diff --git a/docs/examples/inverted-pendulum.en.md b/docs/examples/inverted-pendulum.en.md index 0ca033a5..566b3cd3 100644 --- a/docs/examples/inverted-pendulum.en.md +++ b/docs/examples/inverted-pendulum.en.md @@ -1,93 +1,47 @@ # Inverted Pendulum -`examples/inverted_pendulum/` ships two PPO tasks for the classical cart-pole problem on flat -ground. The extension mirrors `examples/unitree/`: `ManagerBasedRlEnv` over Genesis, rsl_rl PPO, -a `BodyVelocitySensor` on the pole link, and the unified `genelab train` / `genelab play` CLI. +The inverted pendulum example is the recommended first runnable task. It is small enough for smoke +tests but exercises the full GeneLab train/play path. ## Tasks -| Task id | Problem | -|---------|---------| -| `GeneLab-Inverted-Pendulum-v0` | Single inverted pole on a cart. | -| `GeneLab-Double-Inverted-Pendulum-v0` | Two stacked inverted poles on a cart. | +| Task id | Description | +|---|---| +| `GeneLab-Inverted-Pendulum-v0` | Single pole balancing. | +| `GeneLab-Double-Inverted-Pendulum-v0` | Two-link pole balancing. | -## Installation - -Pick the `torch-*` extra that matches the hardware. +## Install and list ```bash -uv sync --extra torch-cu128 uv pip install -e examples/inverted_pendulum - uv run genelab list tasks -# -> GeneLab-Inverted-Pendulum-v0 -# -> GeneLab-Double-Inverted-Pendulum-v0 ``` -## Single inverted pendulum +Without installation: ```bash -uv run genelab train GeneLab-Inverted-Pendulum-v0 \ - --num-envs 4096 --max-iterations 150 - -uv run genelab play GeneLab-Inverted-Pendulum-v0 \ - --checkpoint logs/rsl_rl/inverted_pendulum_flat//model_150.pt --vis +PYTHONPATH=examples/inverted_pendulum/src \ + uv run genelab --import genelab_inverted_pendulum.tasks list tasks ``` -`--checkpoint` makes `play` route through the RL runner with `--agent trained` by default. - -## Double inverted pendulum +## Run ```bash -uv run genelab train GeneLab-Double-Inverted-Pendulum-v0 \ - --num-envs 4096 --max-iterations 300 - -uv run genelab play GeneLab-Double-Inverted-Pendulum-v0 \ - --checkpoint logs/rsl_rl/double_inverted_pendulum_flat//model_300.pt --vis +uv run genelab play GeneLab-Inverted-Pendulum-v0 --steps 64 +uv run genelab play GeneLab-Inverted-Pendulum-v0 --vis --steps 500 +uv run genelab train GeneLab-Inverted-Pendulum-v0 --num_envs 64 --max_iterations 2 ``` -## Sensor and underactuation - -Only the cart slide joint is PD-controlled. The pole hinges default to `kp=0, kv=0` so the -pendulum stays underactuated and the policy must learn balance through cart motion alone. A -`BodyVelocitySensor` attached to the top pole supplies a noisy angular-velocity observation -(corrupted with `Unoise` in the policy group, clean in the critic group). - -## Interactive disturbance - -Play mode launches a single environment (`num_envs=1`) and enables Genesis' -`MouseInteractionPlugin`. Left-click on the cart or pole and drag — a spring force pulls the -clicked link toward the cursor while the policy keeps balancing. Scroll wheel rotates the drag -plane around the surface normal. Release the button to remove the force. - -## Live pole-angle plot - -In play mode, both env configs wire a `RecordingCfg` that reads -`env.robot_state.joint_pos / joint_vel` for the pole hinge(s) of env 0 and feeds them to a -single `PyQtPlotCfg` window with two stacked subplots — angle (rad) on top, angular velocity -(rad/s) below. Joint convention: `pole_hinge = 0` corresponds to the pole vertically up, the -same zero used by the `pole_upright` / `pole_angle_l2` rewards. The double-pendulum config -plots both poles in the same subplots with separate series. History length is 400 ticks -(≈2 s at the play decimation). The recorder is only attached when `play=True`, so the -4096-env training rollout is not slowed by callable invocations. - -!!! tip "Smoke-test budget" - A 5–10 iteration run with `--num-envs 64 --max-iterations 5` is enough to validate wiring - end-to-end. The reward signal will still be noisy at that scale; convergence requires the - 150 / 300 iteration budgets above. - -## Logs - -Both tasks write to `logs/rsl_rl//_/` like the Unitree examples: +## Code entry points -- `params/env.json` and `params/agent.json` — frozen configs at run time. -- `model_.pt` — checkpoints saved every `save_interval` iterations. -- TensorBoard event files alongside the checkpoints. +| File | Role | +|---|---| +| `tasks.py` | Registers robots, envs, and tasks. | +| `single/env_cfg.py` | Single-pole manager-based env config. | +| `double/env_cfg.py` | Double-pole manager-based env config. | +| `mdp.py` | Example-specific reward and termination helpers. | ## See also -- [Unitree G1 quickstart](../getting-started/quickstart.md#unitree-g1) -- [Sensors](../concepts/sensors.md) -- [Recording and plotting](../concepts/recording.md) -- [Managers and MDP terms](../concepts/managers.md) -- [Play and Train CLI](../cli/play-train.md) +- [Tutorial](../tutorial.md) +- [Task design](../best-practices/task-design.md) diff --git a/docs/examples/inverted-pendulum.zh.md b/docs/examples/inverted-pendulum.zh.md index 10fad7e2..04a99cb2 100644 --- a/docs/examples/inverted-pendulum.zh.md +++ b/docs/examples/inverted-pendulum.zh.md @@ -1,88 +1,46 @@ # 倒立摆 -`examples/inverted_pendulum/` 提供两个在平面上的 cart-pole 经典控制 PPO 任务。整条训练栈与 -`examples/unitree/` 对齐:基于 Genesis 的 `ManagerBasedRlEnv`、rsl_rl PPO、挂在杆上的 -`BodyVelocitySensor`,以及统一的 `genelab train` / `genelab play` CLI。 +倒立摆示例是推荐的第一个可运行任务。它足够小,适合 smoke test,同时覆盖完整的 GeneLab train/play 路径。 -## 任务列表 +## 任务 -| Task id | 问题 | -|---------|------| -| `GeneLab-Inverted-Pendulum-v0` | 小车 + 单杆的倒立摆。 | -| `GeneLab-Double-Inverted-Pendulum-v0` | 小车 + 串联双杆的倒立摆。 | +| Task id | 描述 | +|---|---| +| `GeneLab-Inverted-Pendulum-v0` | 单杆平衡。 | +| `GeneLab-Double-Inverted-Pendulum-v0` | 双连杆平衡。 | -## 安装 - -`torch-*` extra 按硬件挑选。 +## 安装并列出 ```bash -uv sync --extra torch-cu128 uv pip install -e examples/inverted_pendulum - uv run genelab list tasks -# -> GeneLab-Inverted-Pendulum-v0 -# -> GeneLab-Double-Inverted-Pendulum-v0 ``` -## 单倒立摆 +不安装时: ```bash -uv run genelab train GeneLab-Inverted-Pendulum-v0 \ - --num-envs 4096 --max-iterations 150 - -uv run genelab play GeneLab-Inverted-Pendulum-v0 \ - --checkpoint logs/rsl_rl/inverted_pendulum_flat//model_150.pt --vis +PYTHONPATH=examples/inverted_pendulum/src \ + uv run genelab --import genelab_inverted_pendulum.tasks list tasks ``` -传入 `--checkpoint` 会让 `play` 自动经过 RL runner,并默认使用 `--agent trained`。 - -## 双倒立摆 +## 运行 ```bash -uv run genelab train GeneLab-Double-Inverted-Pendulum-v0 \ - --num-envs 4096 --max-iterations 300 - -uv run genelab play GeneLab-Double-Inverted-Pendulum-v0 \ - --checkpoint logs/rsl_rl/double_inverted_pendulum_flat//model_300.pt --vis +uv run genelab play GeneLab-Inverted-Pendulum-v0 --steps 64 +uv run genelab play GeneLab-Inverted-Pendulum-v0 --vis --steps 500 +uv run genelab train GeneLab-Inverted-Pendulum-v0 --num_envs 64 --max_iterations 2 ``` -## 传感器与欠驱动 - -只有小车的 slide 关节通过 PD 控制。两个 pole hinge 默认 `kp=0, kv=0`,保证整体处于欠驱动状态, -策略必须通过小车水平运动间接稳定杆。顶端 pole 上挂载的 `BodyVelocitySensor` 给出一路带噪声的 -角速度观测:policy 观测组用 `Unoise` 做 corruption,critic 观测组直接读取干净值。 - -## 交互式扰动 - -`play` 默认只开 1 个环境(`num_envs=1`),并启用 Genesis 的 `MouseInteractionPlugin`。 -左键点击 cart 或 pole 并拖动,会有一根弹簧把所点击的 link 拉向光标位置;策略仍然在背后试图 -保持平衡。滚轮可绕表面法线旋转拖拽平面,松开左键即移除外力。 - -## 杆角度实时曲线 - -`play` 模式下两份 env cfg 都会接入一个 `RecordingCfg`:读 env 0 上 pole hinge 的 -`env.robot_state.joint_pos / joint_vel`,喂给一个 `PyQtPlotCfg` 窗口,里面两幅上下叠放的 -子图——上面是角度(rad),下面是角速度(rad/s)。关节约定 `pole_hinge = 0` 表示杆竖直朝上, -和 `pole_upright` / `pole_angle_l2` reward 的零点一致。双倒立摆把两根杆放在同样的两幅子图里, -每根一条独立曲线。History 长度 400 个 tick(在 play 的 decimation 下约 2 秒)。recorder 只在 -`play=True` 时挂载,避免 4096 env 训练 rollout 因为 callable 调用变慢。 - -!!! tip "Smoke-test 预算" - 使用 `--num-envs 64 --max-iterations 5` 跑 5–10 次迭代足以验证整条链路。此时 reward 信号 - 仍非常嘈杂,真正收敛需要上面给出的 150 / 300 次迭代预算。 - -## 日志 - -两个任务都把日志写到 `logs/rsl_rl//_/`,结构与 Unitree 示例一致: +## 代码入口 -- `params/env.json` 与 `params/agent.json` —— 运行时冻结的配置快照。 -- `model_.pt` —— 按 `save_interval` 保存的 checkpoint。 -- 同目录下的 TensorBoard 事件文件。 +| 文件 | 作用 | +|---|---| +| `tasks.py` | 注册 robot、env、task。 | +| `single/env_cfg.py` | 单杆 manager-based env 配置。 | +| `double/env_cfg.py` | 双杆 manager-based env 配置。 | +| `mdp.py` | 示例自己的 reward 与 termination helper。 | -## See also +## 另见 -- [Unitree G1 快速开始](../getting-started/quickstart.md#unitree-g1) -- [传感器](../concepts/sensors.md) -- [数据录制与绘图](../concepts/recording.md) -- [Manager 与 MDP term](../concepts/managers.md) -- [play 与 train CLI](../cli/play-train.md) +- [教程](../tutorial.md) +- [设计任务](../best-practices/task-design.md) diff --git a/docs/examples/overview.en.md b/docs/examples/overview.en.md index dcd09598..8c6a33ec 100644 --- a/docs/examples/overview.en.md +++ b/docs/examples/overview.en.md @@ -1,72 +1,33 @@ # Examples -The repository ships several reference extensions under `examples/`. They double as -integration tests for the CLI and registry. Every runnable example has its own page -under this section; this overview is the entry point. +Examples are extension packages that exercise GeneLab capabilities without adding project-specific +code to `src/genelab/`. ## Capability map -| Example | Demonstrates | Doc | +| Example | Package | Shows | |---|---|---| -| `inverted_pendulum` | `ManagerBasedRlEnv` + PPO, `BodyVelocitySensor`, `RecordingCfg`, `MouseInteractionPlugin` | [Inverted Pendulum](inverted-pendulum.md) | -| `unitree` | PPO velocity-tracking + motion imitation on the G1 humanoid, asset zoo + motion-clip pipeline | [Unitree G1](unitree-g1.md) | -| `genelab_showcase` | Seven per-feature minimal play tasks (sensors, ray-cast, contact, terrain, curriculum, actuators, recording) | [Showcase](showcase.md) | -| `genelab_examples/rubiks` | Custom play runner, MJCF generated at runtime, Genesis dynamic constraint API | [Rubik's Cube](rubiks-cube.md) | -| `genelab_examples/wuji_hand` | MJCF asset directory, joint-name → DOF mapping, fixed-trajectory replay | [Wuji Hand](wuji-hand.md) | -| `external_project` | Scaffolding template — same shape as `genelab project new` output | see [Project New CLI](../cli/project-new.md) | +| [Inverted Pendulum](inverted-pendulum.md) | `examples/inverted_pendulum` | Minimal train/play loop, manager terms, RSL-RL integration. | +| [Unitree G1](unitree-g1.md) | `examples/unitree` | Humanoid locomotion, velocity commands, motion imitation. | +| [Showcase](showcase.md) | `examples/genelab_showcase` | Sensors, ray casts, contact, terrains, curricula, actuators, recording. | +| [Rubik's Cube](rubiks-cube.md) | `examples/genelab_examples` | Rigid-object composition and visual interaction. | +| [Wuji Hand](wuji-hand.md) | `examples/genelab_examples` | Articulated hand playback and asset packaging. | -## inverted_pendulum +## Install examples -Two PPO cart-pole tasks built on the same `ManagerBasedRlEnv` + rsl_rl stack as the -Unitree example, sized to fit in a laptop training budget: +```bash +uv pip install -e examples/inverted_pendulum +uv pip install -e examples/genelab_examples +uv run genelab list tasks +``` -- **`GeneLab-Inverted-Pendulum-v0`** — single inverted pole on a cart. -- **`GeneLab-Double-Inverted-Pendulum-v0`** — two stacked inverted poles on a cart. +Install Unitree only when needed: -Source at `examples/inverted_pendulum/`. Full walkthrough on the -[Inverted Pendulum](inverted-pendulum.md) page. - -## unitree - -Two PPO tasks on the Unitree G1 humanoid — velocity tracking and motion imitation — -ported from mjlab and adapted to Genesis. Same extension shape as `genelab_examples` -(entry point, `register()`, per-module registration files). Source at -`examples/unitree/`. Full walkthrough on the [Unitree G1](unitree-g1.md) page. - -## genelab_showcase - -Seven play-only tasks, one per GeneLab building block, each driving a real Franka or G1 -into a minimal `ManagerBasedRlEnv` and dumping a per-feature evidence file under -`logs/showcase//`. Designed for human visual / numerical verification rather than -training. Source at `examples/genelab_showcase/`. Full walkthrough on the -[Showcase](showcase.md) page. - -## genelab_examples - -The canonical in-tree extension, wiring two **play-only** Genesis demos that bypass -`ManagerBasedRlEnv`: - -- **`GeneLab-Rubiks-Play-v0`** — 27-cubie cube that dynamically welds and re-articulates - itself through Genesis's constraint API. See [Rubik's Cube](rubiks-cube.md). -- **`GeneLab-Wuji-Hand-Playback-v0`** — Wuji five-finger hand replaying a 20-DoF - `.npy` trajectory through `control_dofs_position`. See [Wuji Hand](wuji-hand.md). - -`pyproject.toml` declares the `genelab.extensions` entry point, so the extension is -discovered automatically once installed. The project's `pyproject.toml` also adds the -source directory to pytest's `pythonpath`, so tests can import from it without -installation. Source at `examples/genelab_examples/`. - -## external_project - -A minimal downstream-project template. `genelab project new` produces a project of the -same shape; this directory is kept in-tree as a reference for the scaffolding output. -It is **not** a runnable example task — instead see the -[Project New CLI](../cli/project-new.md) and [Extensions](../concepts/extensions.md) -pages for how to bootstrap your own downstream project from it. Source at -`examples/external_project/`. +```bash +uv pip install -e examples/unitree +``` ## See also -- [Quickstart](../getting-started/quickstart.md) -- [Extensions](../concepts/extensions.md) -- [Project New CLI](../cli/project-new.md) +- [Tutorial](../tutorial.md) +- [Build an Extension Project](../best-practices/extension-projects.md) diff --git a/docs/examples/overview.zh.md b/docs/examples/overview.zh.md index 60c99724..eb740309 100644 --- a/docs/examples/overview.zh.md +++ b/docs/examples/overview.zh.md @@ -1,62 +1,32 @@ # 示例 -仓库在 `examples/` 下提供数个参考扩展,同时也是 CLI 与注册表的集成测试。每个可运行的示例 -都有独立的文档页面,本页是入口。 +示例是扩展包,用来展示 GeneLab 能力,而不把项目专用代码放进 `src/genelab/`。 -## 能力一览 +## 能力地图 -| 示例 | 演示了什么 | 文档 | +| 示例 | 包 | 展示内容 | |---|---|---| -| `inverted_pendulum` | `ManagerBasedRlEnv` + PPO、`BodyVelocitySensor`、`RecordingCfg`、`MouseInteractionPlugin` | [倒立摆](inverted-pendulum.md) | -| `unitree` | G1 人形机器人速度跟踪 + 动作模仿、asset zoo 与 motion-clip 管线 | [Unitree G1](unitree-g1.md) | -| `genelab_showcase` | 七个单功能 play 任务(sensor、ray-cast、contact、terrain、curriculum、actuator、recording) | [Showcase](showcase.md) | -| `genelab_examples/rubiks` | 自定义 play runner、运行时生成 MJCF、Genesis 动态约束 API | [魔方](rubiks-cube.md) | -| `genelab_examples/wuji_hand` | MJCF 资产目录、关节名 → DOF 映射、固定轨迹重放 | [五指手](wuji-hand.md) | -| `external_project` | 脚手架模板,与 `genelab project new` 产出形状一致 | 见 [Project New CLI](../cli/project-new.md) | +| [倒立摆](inverted-pendulum.md) | `examples/inverted_pendulum` | 最小 train/play 闭环、manager term、RSL-RL 集成。 | +| [Unitree G1](unitree-g1.md) | `examples/unitree` | 人形机器人 locomotion、速度命令、动作模仿。 | +| [Showcase](showcase.md) | `examples/genelab_showcase` | 传感器、ray cast、接触、地形、课程、执行器、录制。 | +| [魔方](rubiks-cube.md) | `examples/genelab_examples` | 刚体组合与可视交互。 | +| [五指手](wuji-hand.md) | `examples/genelab_examples` | 灵巧手 playback 与资产打包。 | -## inverted_pendulum +## 安装示例 -两个 PPO cart-pole 任务,训练栈与 Unitree 示例相同(`ManagerBasedRlEnv` + rsl_rl),训练预算 -控制在单机能跑完的量级: +```bash +uv pip install -e examples/inverted_pendulum +uv pip install -e examples/genelab_examples +uv run genelab list tasks +``` -- **`GeneLab-Inverted-Pendulum-v0`** —— 小车 + 单杆倒立摆。 -- **`GeneLab-Double-Inverted-Pendulum-v0`** —— 小车 + 串联双杆倒立摆。 +只有需要 Unitree 时再安装: -源码位于 `examples/inverted_pendulum/`。完整走读见 [倒立摆](inverted-pendulum.md)。 +```bash +uv pip install -e examples/unitree +``` -## unitree +## 另见 -Unitree G1 人形机器人的两个 PPO 任务 —— 速度跟踪与动作模仿,从 mjlab 移植并适配到 Genesis。 -形态与 `genelab_examples` 相同(entry point、`register()`、按模块拆分的注册文件)。源码位于 -`examples/unitree/`。完整走读见 [Unitree G1](unitree-g1.md)。 - -## genelab_showcase - -7 个 play-only 任务,每一个对应一个 GeneLab 构建块,把真实 Franka 或 G1 投入一个最小的 -`ManagerBasedRlEnv`,并把对应特征的证据落到 `logs/showcase//`。面向人眼/数值核对, -不做训练。源码位于 `examples/genelab_showcase/`。完整走读见 [Showcase](showcase.md)。 - -## genelab_examples - -仓库内的标准扩展,接通两个**绕过 `ManagerBasedRlEnv`** 的 play-only Genesis 演示: - -- **`GeneLab-Rubiks-Play-v0`** —— 27 cubie 的魔方,通过 Genesis 约束 API 动态焊接 / 重新关节化 - 自己。见 [魔方](rubiks-cube.md)。 -- **`GeneLab-Wuji-Hand-Playback-v0`** —— Wuji 五指手用 `control_dofs_position` 重放 20 自由度的 - `.npy` 轨迹。见 [五指手](wuji-hand.md)。 - -`pyproject.toml` 声明了 `genelab.extensions` entry point,因此安装该包后会被自动发现; -项目 `pyproject.toml` 的 `pythonpath` 设置也让测试可以直接 import 而无需安装。源码位于 -`examples/genelab_examples/`。 - -## external_project - -下游项目最小模板。`genelab project new` 生成的内容与之结构一致,留在仓库里作为脚手架输出 -参考。它**不是**一个可运行的示例任务——使用方式见 [Project New CLI](../cli/project-new.md) -和 [扩展加载](../concepts/extensions.md)。源码位于 `examples/external_project/`。 - -## See also - -- [快速开始](../getting-started/quickstart.md) -- [扩展加载](../concepts/extensions.md) -- [Project New CLI](../cli/project-new.md) +- [教程](../tutorial.md) +- [构建扩展项目](../best-practices/extension-projects.md) diff --git a/docs/examples/rubiks-cube.en.md b/docs/examples/rubiks-cube.en.md index 21a7fb73..30f88db0 100644 --- a/docs/examples/rubiks-cube.en.md +++ b/docs/examples/rubiks-cube.en.md @@ -1,116 +1,35 @@ # Rubik's Cube -`examples/genelab_examples/src/genelab_examples/rubiks/` ships a play-only Genesis demo of a -force-driven 3×3 Rubik's cube. The task is **not** an RL task — it bypasses -`ManagerBasedRlEnv` and drives a Genesis `Scene` directly through a custom controller. It -exists primarily as an end-to-end example of (1) registering a non-RL task with the GeneLab -registry, (2) generating an MJCF asset on the fly, and (3) using Genesis's dynamic -constraint API to switch a rigid body between articulation modes. +The Rubik's Cube example is a play-only scene that demonstrates rigid-object composition and visual +interaction. ## Task -| Task id | Problem | -|---------|---------| -| `GeneLab-Rubiks-Play-v0` | A 27-cubie cube that dynamically welds into one rigid body, detects when it has enough angular velocity to "twist", and re-constrains itself into three slabs along the rotating axis. | - -## Installation - -```bash -uv sync --extra torch-cu128 -uv pip install -e examples/genelab_examples - -uv run genelab list tasks | grep Rubiks -# -> GeneLab-Rubiks-Play-v0 -``` - -Or run without installing by adding the example `src` to `PYTHONPATH`: - -```bash -PYTHONPATH=examples/genelab_examples/src \ - uv run genelab --import genelab_examples.tasks list tasks +```text +GeneLab-Rubiks-Play-v0 ``` ## Run ```bash -uv run genelab play GeneLab-Rubiks-Play-v0 --vis --steps 600 - -# Bigger cubie, smaller gap. -uv run genelab play GeneLab-Rubiks-Play-v0 --steps 5 \ - --env.robot.cubie_size 0.04 --env.robot.gap 0.002 - -# Statically weld every cubie (mode comparison). -uv run genelab play GeneLab-Rubiks-Play-v0 --env.robot.welded true --steps 5 - -# Interactive mouse drag (requires --vis). -uv run genelab play GeneLab-Rubiks-Play-v0 --vis --steps 0 \ - --env.interaction.interactive_force true +uv pip install -e examples/genelab_examples +uv run genelab play GeneLab-Rubiks-Play-v0 --vis --steps 500 ``` -`--steps 0` runs the viewer loop until you close the window. Without `--steps` the env -falls back to the value baked into `SimulationCfg.steps`. - -## What this example demonstrates - -| GeneLab capability | Where it appears | Concept doc | -|---|---|---| -| Task registration via `register_task` + `TaskCfg` | `tasks.py` | [Registry](../concepts/registry.md) | -| Robot registration via `register_robot` (MJCF written from a `RubiksCubeSpec`) | `robots.py`, `rubiks/assets.py:write_mjcf` | [Registry](../concepts/registry.md), [Asset zoo](../concepts/asset_zoo.md) | -| Configuration dataclass pattern (`RubiksEnvCfg` extends `ManagerBasedEnvCfg`) | `rubiks/config.py` | [Configs](../concepts/configs.md) | -| Custom `play()` runner that bypasses the manager pipeline | `envs.py:RubiksPlayEnv.play` | Contrasts with [Managers and MDP terms](../concepts/managers.md) | -| Genesis dynamic constraint API (weld / hinge / external torque) | `rubiks/sim.py:ForceDrivenCubeController` | [Scene and entities](../concepts/scene.md) | -| Genesis `MouseInteractionPlugin` for live force drags | `envs.py:RubiksPlayEnv.play` | Same plugin as the [inverted pendulum](inverted-pendulum.md#interactive-disturbance) | - -## Code walkthrough - -The package is split so each file owns one concern: - -- **`rubiks/config.py` (49 lines)** — `RubiksEnvCfg`, `RubiksRobotCfg`, `RubiksInteractionCfg`, - `ForceDrivenCubeConfig`. All overridable through dotted CLI flags - (`--env.robot.cubie_size 0.04`, `--env.interaction.mouse_spring 25`, ...). -- **`rubiks/assets.py` (249 lines)** — Pure geometry. `RubiksCubeSpec` parameterises cubie - size, sticker inset, friction, mass, etc. `iter_cubie_coords` and `exposed_faces` enumerate - the 27-cubie lattice. `write_mjcf` emits the runtime MJCF that Genesis loads. -- **`rubiks/sim.py` (1061 lines)** — The bulk of the example. Two controllers live here: - - `RubiksCubeController` — legacy kinematic turner (`set_qpos` per layer). Used when - `legacy_turn.enabled=true` for visual ground truth. - - `ForceDrivenCubeController` — the default. A state machine that: - 1. Welds all 27 cubies into one rigid body via `solver.add_weld_constraint`. - 2. Watches the welded body's angular velocity. When `|ω| > enter_ang_vel` along an - axis, it deletes the welds *for that axis only* and adds hinge-constraint pairs - so the cube splits into three slabs. - 3. Applies a small `joint_spring + joint_damping` torque through - `solver.apply_links_external_torque` to keep the side slabs at a quantised - quarter-turn rest position. - 4. When the slab angle settles below `exit_angle` *and* below `exit_ang_vel`, it - deletes the hinges and re-welds the slabs back together. - - This length is intrinsic: the cube has 27 cubies × 3 axes × 2 sides × multi-step - settle logic, and every state transition needs Genesis-constraint bookkeeping. The - file is not bloated; trimming it would remove behaviour, not noise. -- **`envs.py:RubiksPlayEnv`** — Builds the Genesis `Scene` (rigid options tuned for the - many-constraint regime: `max_dynamic_constraints=128`, `noslip_iterations=5`), loads the - generated MJCF, optionally attaches `MouseInteractionPlugin`, and runs the controller - step-by-step for `cfg.simulation.steps` ticks. **No `ManagerBasedRlEnv` involved** — this - task does not have observations, actions, or rewards. -- **`tasks.py`** — Registers `GeneLab-Rubiks-Play-v0` as a play-only task - (`trainable=False`). Invoking `genelab train` on it raises `NotImplementedError`. - -## Smoke test +Useful overrides: ```bash -PYTHONPATH=examples/genelab_examples/src \ - uv run genelab --import genelab_examples.tasks play GeneLab-Rubiks-Play-v0 --steps 5 +uv run genelab play GeneLab-Rubiks-Play-v0 --env.robot.cubie_size 0.04 +uv run genelab play GeneLab-Rubiks-Play-v0 --env.robot.welded true ``` -A 5-step play run is enough to validate asset generation, scene construction, and one -controller step. The first run compiles Genesis kernels and is slower. +## Shows + +- Non-RL play task registration. +- Scene composition from many rigid bodies. +- Config overrides for visual parameters. ## See also -- [Wuji Hand](wuji-hand.md) — the sibling play-only demo packaged in the same extension. -- [Registry](../concepts/registry.md) — how `register_task` / `register_robot` work. -- [Configs](../concepts/configs.md) — the dataclass override mechanism that - `--env.robot.welded true` rides on. -- [Scene and entities](../concepts/scene.md) — Genesis primitives used by the controller. -- [Inverted Pendulum](inverted-pendulum.md) — the other example that uses - `MouseInteractionPlugin`. +- [Scene and entities](../concepts/scene.md) +- [Configuration Reference](../reference/configuration.md) diff --git a/docs/examples/rubiks-cube.zh.md b/docs/examples/rubiks-cube.zh.md index 96a31122..102aa270 100644 --- a/docs/examples/rubiks-cube.zh.md +++ b/docs/examples/rubiks-cube.zh.md @@ -1,109 +1,34 @@ # 魔方 -`examples/genelab_examples/src/genelab_examples/rubiks/` 是一个 play-only 的 Genesis 演示, -模拟一个力驱动的 3×3 魔方。这个任务**不是** RL 任务——它绕开 `ManagerBasedRlEnv`, -通过一个自定义控制器直接驱动 Genesis `Scene`。它存在的意义在于演示:(1) 如何在 GeneLab 注册表 -中注册一个非 RL 任务;(2) 如何在运行时即时生成 MJCF 资产;(3) 如何用 Genesis 的动态约束 API -让一个刚体在不同关节形态之间切换。 +魔方示例是 play-only scene,展示刚体组合和可视交互。 -## 任务列表 +## 任务 -| Task id | 问题 | -|---------|------| -| `GeneLab-Rubiks-Play-v0` | 27 个 cubie 起初被动态焊成一整块刚体;一旦绕某根世界轴的角速度足够大,便沿该轴拆成三层平板,每层之间用 hinge 约束相连。 | - -## 安装 - -```bash -uv sync --extra torch-cu128 -uv pip install -e examples/genelab_examples - -uv run genelab list tasks | grep Rubiks -# -> GeneLab-Rubiks-Play-v0 -``` - -或者把 `src` 加到 `PYTHONPATH` 上免安装跑: - -```bash -PYTHONPATH=examples/genelab_examples/src \ - uv run genelab --import genelab_examples.tasks list tasks +```text +GeneLab-Rubiks-Play-v0 ``` ## 运行 ```bash -uv run genelab play GeneLab-Rubiks-Play-v0 --vis --steps 600 - -# 更大的 cubie、更小的间隙。 -uv run genelab play GeneLab-Rubiks-Play-v0 --steps 5 \ - --env.robot.cubie_size 0.04 --env.robot.gap 0.002 - -# 把每个 cubie 静态焊死(用于和默认模式对比)。 -uv run genelab play GeneLab-Rubiks-Play-v0 --env.robot.welded true --steps 5 - -# 鼠标拖拽交互(需要 --vis)。 -uv run genelab play GeneLab-Rubiks-Play-v0 --vis --steps 0 \ - --env.interaction.interactive_force true +uv pip install -e examples/genelab_examples +uv run genelab play GeneLab-Rubiks-Play-v0 --vis --steps 500 ``` -`--steps 0` 让 viewer 循环到窗口被关闭为止。不传 `--steps` 时使用 -`SimulationCfg.steps` 的默认值。 - -## 这个示例演示了什么 - -| GeneLab 能力 | 出现位置 | 概念文档 | -|---|---|---| -| 通过 `register_task` + `TaskCfg` 注册任务 | `tasks.py` | [Registry](../concepts/registry.md) | -| 通过 `register_robot` 注册机器人(MJCF 由 `RubiksCubeSpec` 即时写出) | `robots.py`、`rubiks/assets.py:write_mjcf` | [Registry](../concepts/registry.md)、[Asset zoo](../concepts/asset_zoo.md) | -| 配置 dataclass 模式(`RubiksEnvCfg` 继承 `ManagerBasedEnvCfg`) | `rubiks/config.py` | [Configs](../concepts/configs.md) | -| 自定义 `play()` runner,完全绕过 manager pipeline | `envs.py:RubiksPlayEnv.play` | 对比 [Manager 与 MDP term](../concepts/managers.md) | -| Genesis 动态约束 API(weld / hinge / 外加力矩) | `rubiks/sim.py:ForceDrivenCubeController` | [场景与实体](../concepts/scene.md) | -| Genesis `MouseInteractionPlugin` 实时拖拽 | `envs.py:RubiksPlayEnv.play` | 与[倒立摆](inverted-pendulum.md)使用同一插件 | - -## 代码走读 - -每个文件只负责一件事: - -- **`rubiks/config.py`(49 行)** —— `RubiksEnvCfg`、`RubiksRobotCfg`、 - `RubiksInteractionCfg`、`ForceDrivenCubeConfig`,全部可以通过点号 CLI flag 覆写 - (`--env.robot.cubie_size 0.04`、`--env.interaction.mouse_spring 25` 等)。 -- **`rubiks/assets.py`(249 行)** —— 纯几何。`RubiksCubeSpec` 参数化 cubie 尺寸、贴纸内缩、 - 摩擦、质量等。`iter_cubie_coords` 与 `exposed_faces` 枚举 27 个 cubie。`write_mjcf` - 生成 Genesis 加载的 MJCF。 -- **`rubiks/sim.py`(1061 行)** —— 示例的主要逻辑所在。这里有两个控制器: - - `RubiksCubeController` —— 历史遗留的运动学转动器,靠 `set_qpos` 直接改层位姿, - 在 `legacy_turn.enabled=true` 时作为视觉参照保留。 - - `ForceDrivenCubeController` —— 默认控制器,一个状态机: - 1. 用 `solver.add_weld_constraint` 把 27 个 cubie 焊成一整块。 - 2. 监测整块的角速度,发现 `|ω| > enter_ang_vel` 沿某条轴超过阈值时, - **只**沿该轴拆掉对应焊点,并加上 hinge-constraint 对,使 cube 拆成三层平板。 - 3. 通过 `solver.apply_links_external_torque` 施加一个 - `joint_spring + joint_damping` 弹簧+阻尼力矩,把边层拉到最近的四分之一圈位置。 - 4. 当边层角度小于 `exit_angle` **且**角速度小于 `exit_ang_vel`,就拆掉 hinge、 - 重新焊回。 - - 这部分代码长是因为本质复杂:27 个 cubie × 3 根轴 × 2 个方向 × 多步稳定逻辑, - 每次状态切换都要做 Genesis 约束簿记。删掉任何一段都会改变行为,并非"水分"。 -- **`envs.py:RubiksPlayEnv`** —— 构造 Genesis `Scene`(针对大量约束调优 rigid options: - `max_dynamic_constraints=128`、`noslip_iterations=5`),加载生成的 MJCF,按需挂上 - `MouseInteractionPlugin`,然后跑 `cfg.simulation.steps` 步控制器循环。**没有 - `ManagerBasedRlEnv` 参与**——这个任务没有 obs、action、reward。 -- **`tasks.py`** —— 把 `GeneLab-Rubiks-Play-v0` 注册为 play-only 任务 - (`trainable=False`),对它调用 `genelab train` 会直接抛 `NotImplementedError`。 - -## Smoke test +常用 override: ```bash -PYTHONPATH=examples/genelab_examples/src \ - uv run genelab --import genelab_examples.tasks play GeneLab-Rubiks-Play-v0 --steps 5 +uv run genelab play GeneLab-Rubiks-Play-v0 --env.robot.cubie_size 0.04 +uv run genelab play GeneLab-Rubiks-Play-v0 --env.robot.welded true ``` -5 步就足够验证资产生成、场景构造和一步控制器逻辑。首次运行会编译 Genesis kernel, -比较慢,属于正常现象。 +## 展示内容 + +- 非 RL play task 注册。 +- 多刚体 scene 组合。 +- 可视参数配置 override。 -## See also +## 另见 -- [五指手](wuji-hand.md) —— 同一个 extension 里另一个 play-only 示例。 -- [Registry](../concepts/registry.md) —— `register_task` / `register_robot` 的工作机制。 -- [Configs](../concepts/configs.md) —— `--env.robot.welded true` 走的 dataclass 覆写机制。 -- [场景与实体](../concepts/scene.md) —— 控制器用到的 Genesis 原语。 -- [倒立摆](inverted-pendulum.md) —— 另一个用 `MouseInteractionPlugin` 的示例。 +- [场景与实体](../concepts/scene.md) +- [配置参考](../reference/configuration.md) diff --git a/docs/examples/showcase.en.md b/docs/examples/showcase.en.md index b05e9b16..a2bb7406 100644 --- a/docs/examples/showcase.en.md +++ b/docs/examples/showcase.en.md @@ -1,198 +1,31 @@ # Showcase -`examples/genelab_showcase/` ships seven play-only tasks that exercise every M1–M4 -building block in a single command. Each task drops a real robot — Franka or -Unitree G1 — into a minimal `ManagerBasedRlEnv`, runs a scripted action loop, and -writes a per-feature evidence file under `logs/showcase//`. The intent is -human visual / numerical verification, not training. +`examples/genelab_showcase` contains focused tasks for individual framework capabilities. Use it +when you want a runnable example of one subsystem without reading a full robot task. -## Shared runner +## Capabilities -All seven tasks share the same `ShowcaseRunner` base class in -`examples/genelab_showcase/src/genelab_showcase/runner.py`. It owns the env lifecycle, -the fixed-length scripted-action loop, log-directory creation, and viewer ticking. -Subclasses override two hooks: `_scripted_action(env, step)` to return the per-step -action tensor, and `_dump(env, step, log_root)` to write whatever evidence is relevant -(PNG frames, histograms, tracking-error logs). The recording showcase is the one -exception — it leaves `_dump` untouched because the live plots and file writers run on -Genesis's recorder threads, driven entirely by the env-cfg `recordings=(...)` tuple. +| Area | Demonstrates | +|---|---| +| Sensors | Body velocity, IMU-like state, contact, ray-cast, terrain height. | +| Contact | Contact forces, air time, landing and slip metrics. | +| Terrain | Generated terrain grids and height scans. | +| Curriculum | Terrain-level progression. | +| Actuators | Switching actuator models and action behavior. | +| Recording | Live plots, files, and video-oriented recording configs. | -## Tasks - -| Task id | Robot | Feature exercised | -|---|---|---| -| `GeneLab-Sensors-Showcase-v0` | Franka | `CameraSensor` (RGB+depth PNG dump), `IMUSensor`, `FrameTransformerSensor` | -| `GeneLab-RayCast-Showcase-v0` | Franka | `RayCastSensor` × 3 patterns (`GridPattern`, `RingPattern`, `HemispherePattern`) | -| `GeneLab-Contact-Showcase-v0` | Unitree G1 | `ContactSensor` with `track_air_time=True` on both ankle-roll links | -| `GeneLab-Terrain-Showcase-v0` | Unitree G1 | `TerrainGeneratorCfg` 1×5 row of the five built-in sub-terrains | -| `GeneLab-Curriculum-Showcase-v0` | Unitree G1 | `terrain_levels_vel` curriculum on a 5×5 RandomRough grid | -| `GeneLab-Actuator-Showcase-v0` | Franka | `IdealPDActuator` arm (force-channel control); tracking-error dump | -| `GeneLab-Recording-Showcase-v0` | Franka | `genelab.recording` — live PyQt + MPL plots, NPZ + CSV dumps from an IMU | - -## Installation +## Run ```bash -uv sync --extra torch-cu128 # or whichever torch-* extra matches the host uv pip install -e examples/genelab_showcase - -uv run genelab list tasks | grep Showcase -``` - -The showcase extension auto-loads through the `genelab.extensions` entry point once -installed; no `--import` flag is needed. - -## Sensors - -```bash -uv run genelab play GeneLab-Sensors-Showcase-v0 --vis --steps 200 -``` - -Joint 1 is driven by a slow sine (±0.5 rad over a 4 s period) while the rest of the -arm holds the Menagerie home pose. Every 20 control steps the runner dumps: - -- `logs/showcase/sensors/rgb_.png` — wrist-camera RGB at 160×120. -- `logs/showcase/sensors/depth_.png` — wrist-camera depth as 16-bit gray scaled - to the camera's [near, far] = [0.02, 2.5] m clipping range. -- `logs/showcase/sensors/frame.log` — single-line record of IMU orientation, IMU - linear / angular acceleration, and end-effector + link 7 pose in the base frame. - -!!! warning "BatchRenderer is the only parallel-env backend" - `InteractiveSceneCfg.batch_render=True` is already set in the env config. The - sensor showcase therefore requires Linux x86-64 + CUDA + a Genesis build with - Madrona compiled in. On other platforms the env constructor will raise as soon - as Genesis allocates the renderer. - -**See also**: [`CameraSensor` / `IMUSensor` / `FrameTransformerSensor`](../concepts/sensors.md). - -## Ray-cast patterns - -```bash -uv run genelab play GeneLab-RayCast-Showcase-v0 --vis --steps 200 +uv run genelab list tasks +uv run genelab play --vis --steps 300 ``` -Three `RayCastSensor` instances co-exist on the Franka base — one `GridPattern` -(81 rays, 0.8×0.8 m), one `RingPattern` (32 horizontal × 1 vertical, full ±180°), and -one `HemispherePattern` (128 Fibonacci-distributed rays, 70° polar cap pointing down). -Every 20 steps the runner appends a three-line block to -`logs/showcase/raycast/distances.log`: - -``` -step=0020 - raycast_grid: rays=81 min=… mean=… max=… - raycast_ring: rays=32 min=… mean=… max=… - raycast_hemi: rays=128 min=… mean=… max=… -``` - -`max` clamps at the configured `max_distance`; rays that miss the ground plane (rare -when the pole axis points down) take that ceiling value. - -**See also**: [Ray-cast patterns](../concepts/sensors.md#ray-cast-patterns). - -## Contact and air time - -```bash -uv run genelab play GeneLab-Contact-Showcase-v0 --vis --steps 200 -``` - -The G1 settles into its default standing pose; both feet make ground contact almost -immediately. `logs/showcase/contact/air_time.log` records — every 20 steps — the -per-foot `found` flag, the running `current_contact_time` / `current_air_time`, and -the last completed `last_contact_time` / `last_air_time` snapshots. - -A clean reset cycle every 4 s (`episode_length_s=4`) lets the air-time counters tick -through several boundaries so the snapshot semantics are visible in the log. - -**See also**: [`ContactSensor`](../concepts/sensors.md#contactsensor). - -## Terrains - -```bash -uv run genelab play GeneLab-Terrain-Showcase-v0 --vis --steps 200 -``` - -A 1×5 sub-terrain row instantiates each built-in surface in a single scene: - -| Column | Sub-terrain | Visible feature | -|---|---|---| -| 0 | `FlatPatchCfg` | Flat reference patch at z=0 | -| 1 | `PyramidStairsCfg(step_width=0.4, step_height=-0.08)` | Concentric descending stairs | -| 2 | `RandomRoughCfg(min=-0.08, max=0.08)` | Uniform bumps | -| 3 | `SlopeCfg(slope=-0.25)` | Linear incline | -| 4 | `WaveCfg(num_waves=2, amplitude=0.08)` | Sinusoidal swell | - -G1 spawns at the row centre with default pose and is allowed to fall. A small -downward `GridPattern` ray-cast on the pelvis records the local heightfield response -into `logs/showcase/terrain/terrain.log`. - -**See also**: [Terrains](../concepts/terrains.md). - -## Curriculum - -```bash -uv run genelab play GeneLab-Curriculum-Showcase-v0 --vis --steps 400 -``` - -16 G1 instances spawn on a 5×5 `RandomRoughCfg` grid where row index controls -roughness amplitude. The runner teleports half the envs 1.5 m forward of spawn every -30 control steps to trip the `walked > distance_threshold` branch on auto-reset; the -remaining envs sit still and demote toward level 0. `logs/showcase/curriculum/levels.log` -appends a per-env level vector plus the row histogram every 30 steps. - -**See also**: [Managers and MDP terms](../concepts/managers.md), [Terrains](../concepts/terrains.md). - -## Actuators - -```bash -uv run genelab play GeneLab-Actuator-Showcase-v0 --vis --steps 200 -``` - -Franka is rebuilt with `IdealPDActuator` driving the seven arm joints — same -`stiffness=400`, `damping=80` numbers as the asset-zoo default, but routed through -`control_dofs_force` instead of Genesis's implicit PD. A 0.8-rad sine target is sent -to joint 1; `logs/showcase/actuators/tracking.log` records target vs actual position -and joint velocity every 20 steps so the tracking quality is directly comparable to -the sensors showcase (which uses `ImplicitPDActuator`). - -**See also**: [Actuators](../concepts/actuators.md). - -## Recording - -```bash -uv run genelab play GeneLab-Recording-Showcase-v0 --vis --steps 400 -``` - -Franka with one `IMUSensor` on the hand link plus three `RecordingCfg` sinks defined on -the env config: - -- IMU `lin_acc_b` → `PyQtPlotCfg` live window **and** `CSVFileCfg` dump to - `logs/showcase/recording/lin_acc.csv`. -- IMU `orientation` → `NPZFileCfg` per-episode dump to - `logs/showcase/recording/orientation_*.npz` (`save_on_reset=True`). -- A custom callable returning `env.robot_state.joint_pos[:, 0]` → `MPLPlotCfg` live - window. - -The scripted runner just waves joint 1 with a 0.5 rad / 4 s sine; the recorders run on -Genesis's own recorder threads, so no `_dump` hook is needed. - -**See also**: [Recording and plotting](../concepts/recording.md). - -!!! tip "Smoke-test budget" - Every showcase finishes in well under a minute at the default 200 / 400 steps on - a single env. Increase `--steps` only when accumulating longer log evidence; - none of the showcases benefit from large parallel-env counts. - -## Logs - -Each showcase writes to `logs/showcase//` with the slugs `sensors`, `raycast`, -`contact`, `terrain`, `curriculum`, `actuators`, `recording`. PNG / CSV / NPZ dumps land -alongside the text logs so a single directory is all that needs archiving when -comparing runs. +Use `genelab info ` to inspect the exact override paths for each showcase task. ## See also - [Sensors](../concepts/sensors.md) -- [Terrains](../concepts/terrains.md) -- [Actuators](../concepts/actuators.md) - [Recording and plotting](../concepts/recording.md) -- [Managers and MDP terms](../concepts/managers.md) -- [Scene and entities](../concepts/scene.md) +- [Terrains](../concepts/terrains.md) diff --git a/docs/examples/showcase.zh.md b/docs/examples/showcase.zh.md index 70895ce5..7567494c 100644 --- a/docs/examples/showcase.zh.md +++ b/docs/examples/showcase.zh.md @@ -1,181 +1,30 @@ -# 可视化 showcase +# Showcase -`examples/genelab_showcase/` 提供 7 个 play-only 任务,单条命令就能跑过 M1–M4 全部 -building block。每个任务把真实机器人 (Franka 或 Unitree G1) 投入一个最小的 -`ManagerBasedRlEnv`,跑一段脚本动作循环,把对应特征的证据写进 -`logs/showcase//`。目标是人眼 / 数值核对,不做训练。 +`examples/genelab_showcase` 包含面向单个框架能力的聚焦任务。当你想看某个子系统的可运行示例,而不是完整机器人任务时,用它。 -## 共用 runner +## 能力 -7 个任务都继承自 `examples/genelab_showcase/src/genelab_showcase/runner.py` 中的 -`ShowcaseRunner` 基类。它负责 env 生命周期、定长 scripted-action 循环、日志目录创建以及 -viewer tick。子类只需重载两个 hook:`_scripted_action(env, step)` 返回每步动作张量, -`_dump(env, step, log_root)` 写各自的证据文件(PNG 帧、直方图、跟踪误差日志……)。 -Recording showcase 是唯一不重载 `_dump` 的——它的实时图和文件写入全部跑在 Genesis 的 -recorder 线程上,由 env-cfg 的 `recordings=(...)` 元组直接驱动。 +| 区域 | 展示内容 | +|---|---| +| 传感器 | Body velocity、IMU-like state、contact、ray-cast、terrain height。 | +| 接触 | 接触力、air time、landing 和 slip metric。 | +| 地形 | 生成式地形网格和 height scan。 | +| Curriculum | terrain-level progression。 | +| 执行器 | 切换 actuator 模型和 action 行为。 | +| Recording | 实时曲线、文件、面向视频的 recording 配置。 | -## 任务列表 - -| Task id | Robot | 演示的功能 | -|---|---|---| -| `GeneLab-Sensors-Showcase-v0` | Franka | `CameraSensor` (RGB+depth PNG)、`IMUSensor`、`FrameTransformerSensor` | -| `GeneLab-RayCast-Showcase-v0` | Franka | `RayCastSensor` × 3 种 pattern (`GridPattern`、`RingPattern`、`HemispherePattern`) | -| `GeneLab-Contact-Showcase-v0` | Unitree G1 | `ContactSensor` 在双脚踝 link 上 `track_air_time=True` | -| `GeneLab-Terrain-Showcase-v0` | Unitree G1 | `TerrainGeneratorCfg` 1×5 行覆盖 5 种内置 sub-terrain | -| `GeneLab-Curriculum-Showcase-v0` | Unitree G1 | 5×5 RandomRough 网格上的 `terrain_levels_vel` 课程 | -| `GeneLab-Actuator-Showcase-v0` | Franka | `IdealPDActuator` 驱动机械臂 (force 通道);记录跟踪误差 | -| `GeneLab-Recording-Showcase-v0` | Franka | `genelab.recording` —— 实时 PyQt + MPL 绘图,外加 NPZ + CSV 数据落盘 | - -## 安装 +## 运行 ```bash -uv sync --extra torch-cu128 # 选与本机匹配的 torch-* extra uv pip install -e examples/genelab_showcase - -uv run genelab list tasks | grep Showcase -``` - -装完后 showcase 扩展通过 `genelab.extensions` entry point 自动加载,不需要 `--import`。 - -## 传感器 - -```bash -uv run genelab play GeneLab-Sensors-Showcase-v0 --vis --steps 200 -``` - -Joint 1 跟一段慢正弦 (±0.5 rad,4 秒周期) 摆动,机械臂其余关节保持 Menagerie home 姿态。 -每 20 控制步 dump 一次: - -- `logs/showcase/sensors/rgb_.png` — 腕部相机 RGB,160×120。 -- `logs/showcase/sensors/depth_.png` — 腕部相机深度,16-bit 灰度,按相机 - [near, far] = [0.02, 2.5] m 的裁剪范围拉伸。 -- `logs/showcase/sensors/frame.log` — 单行日志,记录 IMU 姿态、IMU 线/角加速度,以及 - 末端和 link 7 在 base 坐标系下的位置。 - -!!! warning "BatchRenderer 是唯一支持并行 env 的渲染器" - env cfg 已经设 `InteractiveSceneCfg.batch_render=True`。所以这个 showcase 需要 - Linux x86-64 + CUDA + 编译了 Madrona 的 Genesis。其它平台上 env 构造会在 Genesis - 分配 renderer 时立刻报错。 - -**See also**:[`CameraSensor` / `IMUSensor` / `FrameTransformerSensor`](../concepts/sensors.md)。 - -## Ray-cast 形态 - -```bash -uv run genelab play GeneLab-RayCast-Showcase-v0 --vis --steps 200 -``` - -3 个 `RayCastSensor` 并列挂在 Franka 底座上——一个 `GridPattern` (81 条射线、0.8×0.8 m)、 -一个 `RingPattern` (32 横 × 1 纵,±180° 满圈)、一个 `HemispherePattern` (128 条 Fibonacci -分布射线、70° 极轴朝下)。每 20 步往 `logs/showcase/raycast/distances.log` 追加三行: - +uv run genelab list tasks +uv run genelab play --vis --steps 300 ``` -step=0020 - raycast_grid: rays=81 min=… mean=… max=… - raycast_ring: rays=32 min=… mean=… max=… - raycast_hemi: rays=128 min=… mean=… max=… -``` - -`max` 顶到所配的 `max_distance`;没打到地面的射线 (极轴朝下时极少) 也会取这个上限。 - -**See also**:[Ray-cast 形态](../concepts/sensors.md#ray-cast-patterns)。 - -## 接触与离地时间 - -```bash -uv run genelab play GeneLab-Contact-Showcase-v0 --vis --steps 200 -``` - -G1 在默认站立姿态稳定下来,双脚很快与地面接触。`logs/showcase/contact/air_time.log` 每 -20 步记录:双脚的 `found` 旗、运行中的 `current_contact_time` / `current_air_time`,以及 -最近一次完整的 `last_contact_time` / `last_air_time` 快照。 - -每 4 秒一次完整 reset (`episode_length_s=4`) 让 air-time 计数走过若干边界,snapshot -语义在日志里看得清。 - -**See also**:[`ContactSensor`](../concepts/sensors.md#contactsensor)。 - -## 地形 - -```bash -uv run genelab play GeneLab-Terrain-Showcase-v0 --vis --steps 200 -``` - -一行 5 列的 sub-terrain 把 5 种内置地形铺到同一个场景里: - -| 列 | Sub-terrain | 可见特征 | -|---|---|---| -| 0 | `FlatPatchCfg` | z=0 平面参考 | -| 1 | `PyramidStairsCfg(step_width=0.4, step_height=-0.08)` | 同心下降阶梯 | -| 2 | `RandomRoughCfg(min=-0.08, max=0.08)` | 均匀抖动 | -| 3 | `SlopeCfg(slope=-0.25)` | 线性斜坡 | -| 4 | `WaveCfg(num_waves=2, amplitude=0.08)` | 正弦起伏 | - -G1 在 row 中点投放,保持默认姿态自由下落。一个朝下的 `GridPattern` ray-cast 挂在 -pelvis 上,把局部 heightfield 响应写进 `logs/showcase/terrain/terrain.log`。 - -**See also**:[Terrains](../concepts/terrains.md)。 - -## 课程 - -```bash -uv run genelab play GeneLab-Curriculum-Showcase-v0 --vis --steps 400 -``` - -16 个 G1 实例分布在一个 5×5 `RandomRoughCfg` 网格上,行号控制粗糙幅度。runner 每 30 -控制步把一半 env 瞬移 1.5 m,触发 auto-reset 时 `walked > distance_threshold` 分支让它们 -升级;剩下的 env 原地不动,逐步降级到 level 0。`logs/showcase/curriculum/levels.log` -每 30 步追加 per-env level 向量加一个行级直方图。 - -**See also**:[Manager 与 MDP term](../concepts/managers.md)、[Terrains](../concepts/terrains.md)。 - -## 执行器 - -```bash -uv run genelab play GeneLab-Actuator-Showcase-v0 --vis --steps 200 -``` - -Franka 改用 `IdealPDActuator` 驱动 7 个机械臂关节——`stiffness=400` / `damping=80` 数值 -与 asset zoo 默认相同,但走的是 `control_dofs_force` 力通道,不再走 Genesis 内建 PD。 -对 joint 1 发 0.8 rad 正弦目标;`logs/showcase/actuators/tracking.log` 每 20 步记录目标 vs -实际位置和关节速度,可以和 sensors showcase (默认 ImplicitPDActuator) 直接对比跟踪质量。 - -**See also**:[Actuators](../concepts/actuators.md)。 - -## 数据录制 - -```bash -uv run genelab play GeneLab-Recording-Showcase-v0 --vis --steps 400 -``` - -Franka 的 hand link 上挂一个 `IMUSensor`,env cfg 上配三个 `RecordingCfg` 数据流: - -- IMU `lin_acc_b` → `PyQtPlotCfg` 实时窗口 **+** `CSVFileCfg` 写到 - `logs/showcase/recording/lin_acc.csv`。 -- IMU `orientation` → `NPZFileCfg` 按 episode 落盘到 - `logs/showcase/recording/orientation_*.npz`(`save_on_reset=True`)。 -- 一个自定义 callable,返回 `env.robot_state.joint_pos[:, 0]` → `MPLPlotCfg` 实时窗口。 - -脚本 runner 只做一件事:用 0.5 rad / 4 s 的正弦摆动 joint 1。recorder 由 Genesis 自己的 -recorder 线程驱动,所以这个 showcase 不需要重载 `_dump`。 - -**See also**:[数据录制与绘图](../concepts/recording.md)。 - -!!! tip "Smoke-test 预算" - 单 env 默认 200 / 400 步,每个 showcase 都在 1 分钟内跑完。需要积累更长日志证据再 - 加大 `--steps`;并行 env 数量对 showcase 没有好处。 - -## Log 输出 -每个 showcase 写到 `logs/showcase//`,slug 依次是 `sensors`、`raycast`、`contact`、 -`terrain`、`curriculum`、`actuators`、`recording`。PNG / CSV / NPZ dump 和文本日志同目录, -比对运行时归档一个目录即可。 +用 `genelab info ` 查看每个 showcase task 的准确 override 路径。 -## See also +## 另见 - [传感器](../concepts/sensors.md) -- [Terrains](../concepts/terrains.md) -- [Actuators](../concepts/actuators.md) -- [数据录制与绘图](../concepts/recording.md) -- [Manager 与 MDP term](../concepts/managers.md) -- [场景与实体](../concepts/scene.md) +- [数据录制与实时绘图](../concepts/recording.md) +- [地形](../concepts/terrains.md) diff --git a/docs/examples/unitree-g1.en.md b/docs/examples/unitree-g1.en.md index be8f2954..25a38b9f 100644 --- a/docs/examples/unitree-g1.en.md +++ b/docs/examples/unitree-g1.en.md @@ -1,138 +1,50 @@ # Unitree G1 -`examples/unitree/` ships two PPO tasks for the Unitree G1 humanoid on flat ground. -Both tasks are ports of the equivalent `mjlab` recipes adapted to Genesis. +The Unitree G1 example is the advanced robot path after the inverted pendulum tutorial. It shows how +GeneLab scales to humanoid locomotion and motion imitation. ## Tasks -| Task id | Problem | -|---------|---------| -| `Genelab-Velocity-Flat-Unitree-G1-v0` | Velocity-tracking baseline — follow a commanded body-frame twist. | -| `Genelab-Tracking-Flat-Unitree-G1-v0` | Motion imitation — track a recorded clip per-body (BeyondMimic style). | +| Task id | Shows | +|---|---| +| `Genelab-Velocity-Flat-Unitree-G1-v0` | Velocity tracking on flat ground. | +| `Genelab-Tracking-Flat-Unitree-G1-v0` | Reference motion tracking. | -## Installation - -Pick the `torch-*` extra that matches the host. +## Install ```bash -uv sync --extra torch-cu128 uv pip install -e examples/unitree - uv run genelab list tasks -# -> Genelab-Velocity-Flat-Unitree-G1-v0 -# -> Genelab-Tracking-Flat-Unitree-G1-v0 ``` ## Velocity tracking ```bash +uv run genelab play Genelab-Velocity-Flat-Unitree-G1-v0 --vis --steps 500 uv run genelab train Genelab-Velocity-Flat-Unitree-G1-v0 \ - --num-envs 4096 --max-iterations 1500 - -uv run genelab play Genelab-Velocity-Flat-Unitree-G1-v0 \ - --checkpoint logs/rsl_rl/g1_velocity_flat//model_1500.pt --vis + --num_envs 4096 \ + --max_iterations 1500 ``` -## Motion imitation - -The tracking task consumes an NPZ motion clip (keys: `joint_pos`, `joint_vel`, -`body_pos_w`, `body_quat_w`, `body_lin_vel_w`, `body_ang_vel_w` — the schema mjlab's -`csv_to_npz` emits). The default clip is the LAFAN1 retargeted `dance1_subject2` NPZ -fetched on first use by `genelab.asset_zoo.unitree_g1_motions.g1_lafan1_dance1_subject2` -and cached under `.cache/assets/g1_lafan1_dance1_subject2//`. No manual download -required — the example runs out of the box. +Replay: ```bash -# Replay the reference clip frame-by-frame (no policy; robot snaps to motion each step). -# The right way to visually verify the env wiring before training. -uv run python -m genelab_unitree.replay_motion - -# Train. -uv run genelab train Genelab-Tracking-Flat-Unitree-G1-v0 \ - --num-envs 4096 --max-iterations 30000 - -# Replay the trained policy. -uv run genelab play Genelab-Tracking-Flat-Unitree-G1-v0 \ - --agent trained \ - --checkpoint logs/rsl_rl/g1_tracking_flat//model_30000.pt +uv run genelab play Genelab-Velocity-Flat-Unitree-G1-v0 \ + --checkpoint logs/rsl_rl/g1_velocity_flat//model_1500.pt ``` -`--agent` accepts `zero`, `random`, or `trained`. Without `--agent`, play defaults to -`trained` when `--checkpoint` is set, otherwise `zero`. `zero` and `random` only reset -the robot to the clip's first frame and then apply zero / random torques — the robot -will fall, not follow the motion. Use the `replay_motion` script above for pure -reference playback. - -### Swapping the clip - -The default `motion_file` is wired in `tracking_env_cfg.unitree_g1_tracking_env_cfg`. -Replace the `g1_lafan1_dance1_subject2()` call with any NPZ matching the schema above, -and update `motion_body_order` / `motion_joint_order` on the -`MotionCommandCfg` if the new clip uses a different body / joint axis ordering. The -[`genelab-assets`](https://github.com/KraHsu/genelab-assets) repo ships -`unitree_g1/motions/scripts/convert.sh`, a recipe that drives mjlab's `csv_to_npz` -forward-kinematics replay for arbitrary G1-retargeted CSVs. - -The bundled clip inherits its upstream license (CC BY-NC-ND 4.0 — non-commercial -research use, attribution required); see `unitree_g1/motions/LICENSE.NOTICE` in the -assets repo. - -## Long training runs - -For multi-hour training (the tracking task defaults to `max_iterations=30_000` — -several wall-clock hours on 8×H200), set PyTorch's allocator to use growable segments -**before** launching training. This dramatically reduces fragmentation-driven -slowdown over time. +## Motion imitation ```bash -export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True +uv run python -m genelab_unitree.replay_motion +uv run genelab train Genelab-Tracking-Flat-Unitree-G1-v0 \ + --num_envs 4096 \ + --max_iterations 30000 ``` -If training time drifts upward across iterations anyway, capture a profiler trace by -re-running with `GENELAB_PROFILE=1`. The trace lands under `logs/torch_profile/` and -can be loaded with `tensorboard --logdir logs/torch_profile`. Take one trace at the -start and another several hours in to compare cumulative time per section. Optional -env vars: `GENELAB_PROFILE_OUT`, `GENELAB_PROFILE_WAIT`, `GENELAB_PROFILE_WARMUP`, -`GENELAB_PROFILE_ACTIVE`, `GENELAB_PROFILE_REPEAT` (see `src/genelab/rl/_profiler.py`). -Only rank 0 emits a trace under distributed launches. - -!!! tip "Smoke-test budget" - A 5–10 iteration run with `--num-envs 64 --max-iterations 5` is enough to validate - the env wiring end-to-end. The reward signal stays noisy at that scale; - convergence on the velocity task needs the 1500-iteration budget above. - -## Notes - -- The G1 MJCF and STL meshes are fetched on first use via - `genelab.asset_zoo.unitree_g1.UnitreeG1Cfg` from the `genelab-assets` repository and - cached under `.cache/`. They originate from Unitree's `mujoco_menagerie` release; - refer to that repository for licensing details. -- The default motion NPZ (`dance1_subject2`) is similarly fetched from - `genelab-assets` on first use. The clip stores bodies and joints in mjlab's MJCF - DFS order; the env config permutes both axes into Genesis's traversal order via - `motion_body_order` and `motion_joint_order` on `MotionCommandCfg`. -- The motion-imitation task drops mjlab's adaptive-bin failure sampling; only - `start` and `uniform` sampling modes are wired up. Self-collision penalties are - omitted because GeneLab has no contact-pair sensor abstraction yet — the env still - penalises action rate and joint-limit excursions. -- `policy` and `critic` observation groups differ in the tracking task: the critic - receives the privileged per-body pose / orientation features that the actor does - not see. - -## Logs - -Both tasks write to `logs/rsl_rl///` exactly like the inverted -pendulum examples: - -- `params/env.json` and `params/agent.json` — frozen configs at run time. -- `model_.pt` — checkpoints saved every `save_interval` iterations. -- TensorBoard event files alongside the checkpoints. +The default clip is fetched through the asset zoo and cached locally. ## See also -- [Inverted Pendulum](inverted-pendulum.md) -- [Wuji Hand](wuji-hand.md) — smaller fixed-trajectory replay example -- [Actuators](../concepts/actuators.md) +- [RL experiments](../best-practices/rl-experiments.md) - [Asset zoo](../concepts/asset_zoo.md) -- [Sensors](../concepts/sensors.md) -- [Play and Train CLI](../cli/play-train.md) diff --git a/docs/examples/unitree-g1.zh.md b/docs/examples/unitree-g1.zh.md index 08bd4145..949e1316 100644 --- a/docs/examples/unitree-g1.zh.md +++ b/docs/examples/unitree-g1.zh.md @@ -1,125 +1,49 @@ # Unitree G1 -`examples/unitree/` 在平地上提供 2 个 PPO 任务,机器人是 Unitree G1 人形。两个任务都 -对标 `mjlab` 的同名 recipe,移植到 Genesis 上。 +Unitree G1 示例是倒立摆教程后的进阶机器人路径。它展示 GeneLab 如何扩展到人形机器人 locomotion 和动作模仿。 -## 任务列表 +## 任务 -| Task id | 问题 | -|---------|---------| -| `Genelab-Velocity-Flat-Unitree-G1-v0` | 速度跟踪基线——跟踪 body 系的命令 twist。 | -| `Genelab-Tracking-Flat-Unitree-G1-v0` | 动作模仿——按 body 跟踪录制 clip (BeyondMimic 风格)。 | +| Task id | 展示内容 | +|---|---| +| `Genelab-Velocity-Flat-Unitree-G1-v0` | 平地速度跟踪。 | +| `Genelab-Tracking-Flat-Unitree-G1-v0` | 参考动作跟踪。 | ## 安装 -`torch-*` extra 按本机硬件挑。 - ```bash -uv sync --extra torch-cu128 uv pip install -e examples/unitree - uv run genelab list tasks -# -> Genelab-Velocity-Flat-Unitree-G1-v0 -# -> Genelab-Tracking-Flat-Unitree-G1-v0 ``` ## 速度跟踪 ```bash +uv run genelab play Genelab-Velocity-Flat-Unitree-G1-v0 --vis --steps 500 uv run genelab train Genelab-Velocity-Flat-Unitree-G1-v0 \ - --num-envs 4096 --max-iterations 1500 - -uv run genelab play Genelab-Velocity-Flat-Unitree-G1-v0 \ - --checkpoint logs/rsl_rl/g1_velocity_flat//model_1500.pt --vis + --num_envs 4096 \ + --max_iterations 1500 ``` -## 动作模仿 - -tracking 任务消费 NPZ motion clip (key:`joint_pos`、`joint_vel`、`body_pos_w`、 -`body_quat_w`、`body_lin_vel_w`、`body_ang_vel_w` —— mjlab `csv_to_npz` 产出的 schema)。 -默认 clip 是 LAFAN1 retargeted 的 `dance1_subject2` NPZ,首次使用时由 -`genelab.asset_zoo.unitree_g1_motions.g1_lafan1_dance1_subject2` 拉取并缓存到 -`.cache/assets/g1_lafan1_dance1_subject2//`,无需手动下载,example 开箱即用。 +回放: ```bash -# 逐帧回放参考 clip (不接策略;机器人每步贴到 motion 当前帧)。 -# 训练前验证 env 接线是否正确的标准方式。 -uv run python -m genelab_unitree.replay_motion - -# 训练。 -uv run genelab train Genelab-Tracking-Flat-Unitree-G1-v0 \ - --num-envs 4096 --max-iterations 30000 - -# 回放训练好的策略。 -uv run genelab play Genelab-Tracking-Flat-Unitree-G1-v0 \ - --agent trained \ - --checkpoint logs/rsl_rl/g1_tracking_flat//model_30000.pt +uv run genelab play Genelab-Velocity-Flat-Unitree-G1-v0 \ + --checkpoint logs/rsl_rl/g1_velocity_flat//model_1500.pt ``` -`--agent` 取 `zero`、`random` 或 `trained`。不写 `--agent` 时,`--checkpoint` 存在则默认 -`trained`,否则 `zero`。`zero` 和 `random` 都只在 reset 时把机器人摆到 clip 第 0 帧、 -之后施加零 / 随机力矩,机器人只会倒下,不会跟随动作。要做无策略的参考回放,用上面的 -`replay_motion` 脚本。 - -### 替换 clip - -默认 `motion_file` 在 `tracking_env_cfg.unitree_g1_tracking_env_cfg` 里硬编码。把 -`g1_lafan1_dance1_subject2()` 替换成符合上面 schema 的任意 NPZ 路径即可;若新 clip 的 -body / joint 轴顺序不同,记得同步更新 `MotionCommandCfg` 的 `motion_body_order` / -`motion_joint_order`。[`genelab-assets`](https://github.com/KraHsu/genelab-assets) 仓库 -里附带 `unitree_g1/motions/scripts/convert.sh`,把任意 G1 retargeted CSV 喂给 mjlab 的 -`csv_to_npz` 做一次正运动学回放。 - -捆绑 clip 继承上游许可 (CC BY-NC-ND 4.0 —— 仅供非商业研究,需署名);详情见 assets 仓库 -里的 `unitree_g1/motions/LICENSE.NOTICE`。 - -## 长时间训练 - -多小时的训练 (tracking 任务默认 `max_iterations=30_000`,在 8×H200 上要数小时墙钟), -启动训练**前**把 PyTorch 内存分配器切到可增长 segment,能显著减少碎片化导致的逐步降速。 +## 动作模仿 ```bash -export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True +uv run python -m genelab_unitree.replay_motion +uv run genelab train Genelab-Tracking-Flat-Unitree-G1-v0 \ + --num_envs 4096 \ + --max_iterations 30000 ``` -如果训练时长仍然在 iteration 间漂移,重跑时加 `GENELAB_PROFILE=1` 捕一段 profiler。 -trace 落在 `logs/torch_profile/`,可用 `tensorboard --logdir logs/torch_profile` 看。 -在训练初期和数小时后各抓一段,对比 per-section 累计时间。可选 env var: -`GENELAB_PROFILE_OUT`、`GENELAB_PROFILE_WAIT`、`GENELAB_PROFILE_WARMUP`、 -`GENELAB_PROFILE_ACTIVE`、`GENELAB_PROFILE_REPEAT` (见 `src/genelab/rl/_profiler.py`)。 -分布式启动时只 rank 0 写 trace。 - -!!! tip "Smoke-test 预算" - `--num-envs 64 --max-iterations 5` 跑 5–10 个 iteration 就能验证 env 接线 OK。这种 - 规模下 reward 信号噪声大;速度任务真正收敛需要上面 1500 iteration 的预算。 - -## 备注 - -- G1 的 MJCF 与 STL mesh 在首次使用时由 `genelab.asset_zoo.unitree_g1.UnitreeG1Cfg` - 从 `genelab-assets` 仓库拉取并缓存到 `.cache/`。源自 Unitree 的 `mujoco_menagerie` - release,license 详见上游仓库。 -- 默认 motion NPZ (`dance1_subject2`) 同样从 `genelab-assets` 首次使用时拉取。clip - 内的 body / joint 轴按 mjlab MJCF 的 DFS 顺序存储;env config 通过 `MotionCommandCfg` - 的 `motion_body_order` / `motion_joint_order` 把两轴重排到 Genesis 的遍历顺序。 -- 动作模仿任务去掉了 mjlab 的 adaptive-bin 失败采样;只接了 `start` 与 `uniform` 两种 - 采样模式。自碰惩罚也省略,因为 GeneLab 还没有接触对 sensor 抽象——env 仍然惩罚动作 - 变化率与关节越界。 -- tracking 任务的 `policy` / `critic` observation group 不同:critic 看得到特权的 per-body - 位姿 / 朝向特征,actor 看不到。 - -## Log 输出 - -两个任务都写到 `logs/rsl_rl///`,与倒立摆任务一致: - -- `params/env.json` 与 `params/agent.json` — 运行时冻结下来的 cfg。 -- `model_.pt` — 每 `save_interval` 个 iteration 一份 checkpoint。 -- TensorBoard event 文件与 checkpoint 同目录。 +默认 clip 通过 asset zoo 获取并缓存到本地。 -## See also +## 另见 -- [倒立摆](inverted-pendulum.md) -- [五指手](wuji-hand.md) —— 更小规模的固定轨迹回放示例 -- [Actuators](../concepts/actuators.md) -- [Asset zoo](../concepts/asset_zoo.md) -- [传感器](../concepts/sensors.md) -- [play 与 train CLI](../cli/play-train.md) +- [运行 RL 实验](../best-practices/rl-experiments.md) +- [资产库](../concepts/asset_zoo.md) diff --git a/docs/examples/wuji-hand.en.md b/docs/examples/wuji-hand.en.md index a3c50b26..2359d1d5 100644 --- a/docs/examples/wuji-hand.en.md +++ b/docs/examples/wuji-hand.en.md @@ -1,109 +1,35 @@ # Wuji Hand -`examples/genelab_examples/src/genelab_examples/wuji_hand/` ships a play-only Genesis -demo of the Wuji five-finger dexterous hand replaying a fixed `.npy` trajectory. Like the -[Rubik's cube example](rubiks-cube.md) it is **not** an RL task — it builds a Genesis -`Scene` directly and PD-controls each joint toward the next trajectory frame. The point -is to show the minimum amount of GeneLab + Genesis glue needed to drive a robot from a -recorded motion clip. +The Wuji Hand example is a play-only articulated-hand playback task. It demonstrates asset packaging +and scripted joint trajectory playback. ## Task -| Task id | Problem | -|---------|---------| -| `GeneLab-Wuji-Hand-Playback-v0` | Load the right-side Wuji MJCF, map each `right_fingerN_jointM` to its Genesis DOF, then replay a 20-column trajectory frame-by-frame through `control_dofs_position`. | - -## Installation - -```bash -uv sync --extra torch-cu128 -uv pip install -e examples/genelab_examples - -uv run genelab list tasks | grep Wuji -# -> GeneLab-Wuji-Hand-Playback-v0 -``` - -Or run without installing: - -```bash -PYTHONPATH=examples/genelab_examples/src \ - uv run genelab --import genelab_examples.tasks list tasks +```text +GeneLab-Wuji-Hand-Playback-v0 ``` ## Run ```bash -# Default trajectory: a 20-DoF wave gesture bundled in the package. -uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --vis --steps 600 - -# Disable periodic hard resets (let the trajectory run uninterrupted). -uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --vis --env.reset_interval 0 - -# Swap to the left-side MJCF (only `right` is bundled by default). -uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --vis --env.robot.side left +uv pip install -e examples/genelab_examples +uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --vis --steps 500 ``` -`reset_interval` is the number of Genesis steps between periodic teleports back to the -joint-zero pose; `0` disables the teleport. The default `wave.npy` ships next to the -package source. - -## What this example demonstrates - -| GeneLab capability | Where it appears | Concept doc | -|---|---|---| -| Task registration of a play-only task | `tasks.py` | [Registry](../concepts/registry.md) | -| Robot registration backed by an on-disk MJCF asset directory | `robots.py`, `wuji_hand/assets.py:resolve_mjcf_path` | [Registry](../concepts/registry.md), [Asset zoo](../concepts/asset_zoo.md) | -| Config dataclass with nested `SimulationCfg` and a default `Path` field | `wuji_hand/config.py` | [Configs](../concepts/configs.md) | -| Joint-name → Genesis DOF-index mapping for trajectory replay | `wuji_hand/sim.py:build_joint_mapping`, `JointMapping` dataclass | n/a (example-specific helper) | -| Trajectory loading from `.npy` + per-joint clamping to DOF limits | `wuji_hand/assets.py:load_trajectory`, `wuji_hand/sim.py:trajectory_target` | Same pattern as the [Unitree G1 motion clip](unitree-g1.md#motion-imitation) | -| Per-joint PD gain and force-range setup at runtime | `wuji_hand/sim.py:apply_wuji_gains` | [Actuators](../concepts/actuators.md) — contrast with the configured-actuator pattern | - -## Code walkthrough - -The package is small (≈400 lines total) and each file owns one concern: - -- **`wuji_hand/config.py` (24 lines)** — `WujiEnvCfg` (extends `ManagerBasedEnvCfg`) and - `WujiRobotCfg`. Default `dt=0.01`, `steps=0` (infinite viewer loop), `reset_interval=500`, - `side="right"`. All overridable via dotted flags - (`--env.simulation.dt 0.005`, `--env.reset_interval 0`, `--env.robot.side left`). -- **`wuji_hand/assets.py` (74 lines)** — File-path resolution and trajectory loading. - - `wuji_joint_names(side)` returns the 20 joint names in the order the trajectory's - columns expect (`{side}_fingerN_jointM` for `N=1..5, M=1..4`). - - `resolve_mjcf_path(desc_dir, side)` tries three candidate paths so users can drop in - their own MJCF layout. - - `load_trajectory(path)` validates shape (2D, ≥20 columns) and returns `float32`. -- **`wuji_hand/sim.py` (280 lines)** — The playback loop. Worth reading in order: - 1. `WujiHandRunConfig` — flat run-time config with `__post_init__` validators. - 2. `build_joint_mapping(entity, side)` — for each trajectory column, look up the matching - Genesis joint by name and record its local DOF index. Missing joints are skipped - with a warning rather than failing. - 3. `apply_wuji_gains` — sets `kp=0.8`, `kv=0.04` on every mapped DOF and copies the - MJCF's declared force range onto Genesis's runtime range (only when both bounds are - finite — MJCFs with `forcerange="0 0"` or unset values are passed through). - 4. `run_wuji_hand(config)` — initialises Genesis, builds the scene (with rigid options - tuned for self-collision on a high-DoF hand), maps joints, then in the main loop - calls `entity.control_dofs_position(target, mapping.dof_indices)` once per Genesis - step using the clamped trajectory frame. -- **`envs.py:WujiHandPlaybackEnv.play`** — Thin wrapper that copies `WujiEnvCfg` fields - into a `WujiHandRunConfig` and calls `run_wuji_hand`. **No `ManagerBasedRlEnv`** — like - the Rubik's example, this task has no obs/action/reward graph. -- **`tasks.py`** — Registers `GeneLab-Wuji-Hand-Playback-v0` with `trainable=False`. - -## Smoke test +Useful overrides: ```bash -PYTHONPATH=examples/genelab_examples/src \ - uv run genelab --import genelab_examples.tasks play GeneLab-Wuji-Hand-Playback-v0 --steps 5 +uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --env.reset_interval 0 +uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --env.robot.side left ``` -A 5-step run validates MJCF resolution, joint mapping, and one trajectory frame. First -launch triggers Genesis kernel compilation. +## Shows + +- Articulated hand asset packaging. +- Playback task structure without an RL runner. +- Configurable reset/playback behavior. ## See also -- [Rubik's Cube](rubiks-cube.md) — sibling play-only demo packaged in the same extension. -- [Unitree G1](unitree-g1.md) — the larger motion-replay example (LAFAN1 NPZ, full - humanoid). -- [Registry](../concepts/registry.md) — how the task and robot are wired in. -- [Configs](../concepts/configs.md) — dataclass override mechanism behind the - `--env.robot.side left` flag. +- [Scene and entities](../concepts/scene.md) +- [Asset zoo](../concepts/asset_zoo.md) diff --git a/docs/examples/wuji-hand.zh.md b/docs/examples/wuji-hand.zh.md index 0ceb237c..71affb90 100644 --- a/docs/examples/wuji-hand.zh.md +++ b/docs/examples/wuji-hand.zh.md @@ -1,101 +1,34 @@ # 五指手 -`examples/genelab_examples/src/genelab_examples/wuji_hand/` 是一个 play-only 的 Genesis 演示, -让 Wuji 五指灵巧手回放一段固定的 `.npy` 轨迹。和[魔方示例](rubiks-cube.md)一样,它**不是** -RL 任务——直接构造 Genesis `Scene`,每个关节用 PD 控制器跟踪下一帧轨迹目标。它的意义在于 -展示用最少的 GeneLab + Genesis 胶水��码就能让机器人按预录动作走完。 +五指手示例是 play-only 灵巧手 playback task,展示资产打包和脚本化关节轨迹回放。 -## 任务列表 +## 任务 -| Task id | 问题 | -|---------|------| -| `GeneLab-Wuji-Hand-Playback-v0` | 加载右手 MJCF,将每个 `right_fingerN_jointM` 与 Genesis DOF 对齐,然后通过 `control_dofs_position` 逐帧重放一段 20 列轨迹。 | - -## 安装 - -```bash -uv sync --extra torch-cu128 -uv pip install -e examples/genelab_examples - -uv run genelab list tasks | grep Wuji -# -> GeneLab-Wuji-Hand-Playback-v0 -``` - -或免安装运行: - -```bash -PYTHONPATH=examples/genelab_examples/src \ - uv run genelab --import genelab_examples.tasks list tasks +```text +GeneLab-Wuji-Hand-Playback-v0 ``` ## 运行 ```bash -# 默认轨迹:包内自带的 20 自由度挥手动作。 -uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --vis --steps 600 - -# 关闭周期性硬复位,让轨迹连续播放。 -uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --vis --env.reset_interval 0 - -# 切到左手 MJCF(默认只带 right)。 -uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --vis --env.robot.side left +uv pip install -e examples/genelab_examples +uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --vis --steps 500 ``` -`reset_interval` 是每隔多少 Genesis 步把手重置回零位姿态的间隔,`0` 表示关闭。默认的 -`wave.npy` 与包源码同目录。 - -## 这个示例演示了什么 - -| GeneLab 能力 | 出现位置 | 概念文档 | -|---|---|---| -| 注册 play-only 任务 | `tasks.py` | [Registry](../concepts/registry.md) | -| 基于磁盘 MJCF 资产目录注册机器人 | `robots.py`、`wuji_hand/assets.py:resolve_mjcf_path` | [Registry](../concepts/registry.md)、[Asset zoo](../concepts/asset_zoo.md) | -| 嵌套 `SimulationCfg`、默认 `Path` 字段的 config dataclass | `wuji_hand/config.py` | [Configs](../concepts/configs.md) | -| 关节名 → Genesis DOF 下标的映射,用于轨迹重放 | `wuji_hand/sim.py:build_joint_mapping`、`JointMapping` dataclass | 无(示例自带的辅助器) | -| 从 `.npy` 加载轨迹 + 按 DOF 上下限逐关节裁剪 | `wuji_hand/assets.py:load_trajectory`、`wuji_hand/sim.py:trajectory_target` | 与 [Unitree G1](unitree-g1.md) 的动作模仿模式相同 | -| 运行时按需设置 PD 增益和力矩范围 | `wuji_hand/sim.py:apply_wuji_gains` | [Actuators](../concepts/actuators.md) —— 与"在配置里直接声明 actuator"模式形成对比 | - -## 代码走读 - -整个包约 400 行,每个文件只做一件事: - -- **`wuji_hand/config.py`(24 行)** —— `WujiEnvCfg`(继承 `ManagerBasedEnvCfg`)和 - `WujiRobotCfg`。默认 `dt=0.01`、`steps=0`(viewer 死循环)、`reset_interval=500`、 - `side="right"`。全部可以通过点号 flag 覆写(`--env.simulation.dt 0.005`、 - `--env.reset_interval 0`、`--env.robot.side left`)。 -- **`wuji_hand/assets.py`(74 行)** —— 路径解析与轨迹加载。 - - `wuji_joint_names(side)` 按轨迹列顺序返回 20 个关节名 - (`{side}_fingerN_jointM`,`N=1..5, M=1..4`)。 - - `resolve_mjcf_path(desc_dir, side)` 顺序尝试三种候选路径,方便用户接入自己的 MJCF 目录布局。 - - `load_trajectory(path)` 校验形状(2D、≥20 列),返回 `float32`。 -- **`wuji_hand/sim.py`(280 行)** —— 重放循环。建议按顺序读: - 1. `WujiHandRunConfig` —— 扁平的运行时配置,`__post_init__` 做基本校验。 - 2. `build_joint_mapping(entity, side)` —— 对每个轨迹列,按名字查找匹配的 Genesis - 关节并记录其本地 DOF 下标。找不到的关节只打 warning 跳过,不直接报错。 - 3. `apply_wuji_gains` —— 给映射上的每个 DOF 设 `kp=0.8`、`kv=0.04`,并把 MJCF - 声明的力矩范围复制到 Genesis 的运行时 force range(仅当上下限都有限时; - `forcerange="0 0"` 或未设值的 MJCF 不动)。 - 4. `run_wuji_hand(config)` —— 初始化 Genesis,按高自由度手的自碰撞需求调好 rigid - options,建场景,做完关节映射后进入主循环:用裁剪过的轨迹帧每步调一次 - `entity.control_dofs_position(target, mapping.dof_indices)`。 -- **`envs.py:WujiHandPlaybackEnv.play`** —— 薄包装,把 `WujiEnvCfg` 的字段拷到一个 - `WujiHandRunConfig`,再调 `run_wuji_hand`。**没有 `ManagerBasedRlEnv`**——和魔方示例 - 一样,这个任务没有 obs/action/reward 图。 -- **`tasks.py`** —— 把 `GeneLab-Wuji-Hand-Playback-v0` 注册为 `trainable=False` 的任务。 - -## Smoke test +常用 override: ```bash -PYTHONPATH=examples/genelab_examples/src \ - uv run genelab --import genelab_examples.tasks play GeneLab-Wuji-Hand-Playback-v0 --steps 5 +uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --env.reset_interval 0 +uv run genelab play GeneLab-Wuji-Hand-Playback-v0 --env.robot.side left ``` -5 步运行足以验证 MJCF 解析、关节映射和一帧轨迹的下发。首次启动会触发 Genesis -kernel 编译。 +## 展示内容 + +- 灵巧手 articulation 资产打包。 +- 不带 RL runner 的 playback task 结构。 +- 可配置 reset/playback 行为。 -## See also +## 另见 -- [魔方](rubiks-cube.md) —— 同一个 extension 里的另一个 play-only 示例。 -- [Unitree G1](unitree-g1.md) —— 更大规模的动作回放示例(LAFAN1 NPZ、整人形机器人)。 -- [Registry](../concepts/registry.md) —— task 和 robot 是怎样接入注册表的。 -- [Configs](../concepts/configs.md) —— `--env.robot.side left` 走的 dataclass 覆写机制。 +- [场景与实体](../concepts/scene.md) +- [资产库](../concepts/asset_zoo.md) diff --git a/docs/getting-started/installation.en.md b/docs/getting-started/installation.en.md index 364ae1df..359806f4 100644 --- a/docs/getting-started/installation.en.md +++ b/docs/getting-started/installation.en.md @@ -1,77 +1,77 @@ # Installation -GeneLab requires **Python 3.12 or newer** and uses [`uv`](https://docs.astral.sh/uv/) for -dependency management. +This page is the focused installation checklist. For the full learning path, start with the +[tutorial](../tutorial.md). -## 1. Sync the environment +## Requirements + +| Requirement | Version | +|---|---| +| Python | 3.12 or newer | +| Dependency manager | `uv` | +| Simulator backend | `genesis-world>=0.4.7` | +| PyTorch | `torch>=2.8.0` through one `torch-*` extra | + +## Sync the environment From the repository root: ```bash -uv sync -uv run genelab --help +uv sync --extra torch-cpu +uv run genelab --version ``` -`uv sync` creates the project virtual environment, installs GeneLab from this checkout, and -installs the dependencies pinned by `uv.lock`. `uv run ...` runs commands inside that -environment. A bare `genelab` command works only after `.venv` is activated or GeneLab is -installed into the active Python environment. - -## 2. Pick exactly one PyTorch extra - -The `torch-*` extras are **mutually exclusive**: +Choose exactly one PyTorch extra: -| Extra | Hardware target | -|-------|----------------| +| Extra | Target | +|---|---| | `torch-cpu` | CPU-only or non-NVIDIA development machines. | -| `torch-cu126` | NVIDIA, CUDA 12.6 driver. | -| `torch-cu128` | NVIDIA, CUDA 12.8 driver. | -| `torch-cu130` | NVIDIA, CUDA 13.0 driver. | +| `torch-cu126` | NVIDIA driver compatible with CUDA 12.6 wheels. | +| `torch-cu128` | NVIDIA driver compatible with CUDA 12.8 wheels. | +| `torch-cu130` | NVIDIA driver compatible with CUDA 13.0 wheels. | + +Refresh an existing torch install: ```bash -uv sync --extra torch-cpu # one of the above +uv sync --reinstall-package torch --extra torch-cu128 ``` -!!! warning "PyTorch version requirement" - Genesis requires `torch>=2.8.0` — older builds emit a `'torch<2.8.0' is not supported` - warning at import time and may break Genesis runtime assumptions. All `torch-*` extras pin - `torch>=2.8.0`, so `uv sync` will pull a compatible wheel automatically. PyTorch only - publishes 2.8+ wheels on the `cpu`, `cu126`, `cu128`, and `cu130` indices; older CUDA - flavours (`cu118` / `cu121` / `cu124`) are intentionally not offered as extras. An older - `torch` already in the environment can be refreshed with - `uv sync --reinstall-package torch --extra torch-cuXXX`. - -Run `nvidia-smi` to confirm the driver version when unsure which CUDA build to use. - -## 3. Initialize project-local caches - -Genesis, Quadrants, and Matplotlib all require a writable cache directory. The CLI sets up both -the directory layout and the matching environment variables: +## Create local caches ```bash uv run genelab cache ``` -This sets `XDG_CACHE_HOME` and `MPLCONFIGDIR` under `.cache/`, so the simulator never writes to -the user's home directory. +The command creates writable project-local cache directories and points `XDG_CACHE_HOME` and +`MPLCONFIGDIR` at `.cache/`. -## 4. Verify +## Verify imports ```bash uv run python -c "import genelab; print(genelab.__version__)" -uv run python -c "from genelab.lab import ManagerBasedEnvCfg; print(ManagerBasedEnvCfg.__name__)" +uv run python -c "from genelab.lab import TaskCfg; print(TaskCfg.__name__)" +uv run python -c "import torch; print(torch.__version__, torch.version.cuda)" uv run python -c "import genesis; print(genesis.__version__)" -uv run pytest -uv run ruff check -uv run pyright ``` -Verify the selected PyTorch build: +## Install example extensions + +The core package is a framework; tasks are registered by extensions. ```bash -uv run python -c "import torch; print(torch.__version__, torch.version.cuda)" +uv pip install -e examples/inverted_pendulum +uv pip install -e examples/genelab_examples +uv run genelab list tasks +``` + +Install Unitree only when you want the larger humanoid examples: + +```bash +uv pip install -e examples/unitree ``` ## See also -- [Quickstart](quickstart.md) +- [Tutorial](../tutorial.md) +- [Debug Common Failures](../best-practices/debugging.md) +- [Build an Extension Project](../best-practices/extension-projects.md) diff --git a/docs/getting-started/installation.zh.md b/docs/getting-started/installation.zh.md index 4048b438..bea19509 100644 --- a/docs/getting-started/installation.zh.md +++ b/docs/getting-started/installation.zh.md @@ -1,71 +1,75 @@ # 安装 -GeneLab 需要 **Python 3.12 或更新版本**,使用 [`uv`](https://docs.astral.sh/uv/) 管理依赖。 +这页是专门的安装检查清单。完整学习路径请从[教程](../tutorial.md)开始。 -## 1. 同步环境 +## 要求 -在仓库根目录运行: +| 要求 | 版本 | +|---|---| +| Python | 3.12 或更新 | +| 依赖管理 | `uv` | +| 仿真后端 | `genesis-world>=0.4.7` | +| PyTorch | 通过一个 `torch-*` extra 安装 `torch>=2.8.0` | + +## 同步环境 + +在仓库根目录: ```bash -uv sync -uv run genelab --help +uv sync --extra torch-cpu +uv run genelab --version ``` -`uv sync` 会创建项目虚拟环境,从当前 checkout 安装 GeneLab,并安装 `uv.lock` 锁定的依赖。 -`uv run ...` 在该环境中运行命令。裸 `genelab` 命令只有在 `.venv` 已激活、或 GeneLab 已安装 -到当前 Python 环境后才可用。 +只能选择一个 PyTorch extra: -## 2. 挑选一个 PyTorch extra +| Extra | 目标 | +|---|---| +| `torch-cpu` | CPU-only 或非 NVIDIA 开发机器。 | +| `torch-cu126` | 兼容 CUDA 12.6 wheel 的 NVIDIA 驱动。 | +| `torch-cu128` | 兼容 CUDA 12.8 wheel 的 NVIDIA 驱动。 | +| `torch-cu130` | 兼容 CUDA 13.0 wheel 的 NVIDIA 驱动。 | -`torch-*` extras **互斥**: - -| Extra | 目标硬件 | -|-------|---------| -| `torch-cpu` | 仅 CPU 或非 NVIDIA 开发机器。 | -| `torch-cu126` | NVIDIA,CUDA 12.6 驱动。 | -| `torch-cu128` | NVIDIA,CUDA 12.8 驱动。 | -| `torch-cu130` | NVIDIA,CUDA 13.0 驱动。 | +刷新已有 torch: ```bash -uv sync --extra torch-cpu # 从上表挑一个 +uv sync --reinstall-package torch --extra torch-cu128 ``` -!!! warning "PyTorch 版本要求" - Genesis 要求 `torch>=2.8.0`,旧版本在导入时会报 `'torch<2.8.0' is not supported`,并可能 - 破坏 Genesis 的运行时假设。所有 `torch-*` extras 都固定 `torch>=2.8.0`,`uv sync` 会自动 - 拉取兼容 wheel。PyTorch 只在 `cpu`、`cu126`、`cu128`、`cu130` 索引提供 2.8+ wheel,因此 - 较旧的 CUDA flavour(`cu118` / `cu121` / `cu124`)不再以 extra 形式提供。环境里已存在 - 旧 torch 时,可用 `uv sync --reinstall-package torch --extra torch-cuXXX` 刷新。 - -不确定使用哪个 CUDA build 时,用 `nvidia-smi` 查看驱动版本。 - -## 3. 初始化项目本地缓存 - -Genesis、Quadrants、Matplotlib 都需要可写缓存目录。CLI 一条命令完成目录与环境变量配置: +## 创建本地缓存 ```bash uv run genelab cache ``` -它会把 `XDG_CACHE_HOME` 和 `MPLCONFIGDIR` 指向 `.cache/`,避免仿真器写到用户主目录。 +该命令创建项目本地可写缓存目录,并把 `XDG_CACHE_HOME` 和 `MPLCONFIGDIR` 指向 `.cache/`。 -## 4. 验证 +## 验证导入 ```bash uv run python -c "import genelab; print(genelab.__version__)" -uv run python -c "from genelab.lab import ManagerBasedEnvCfg; print(ManagerBasedEnvCfg.__name__)" +uv run python -c "from genelab.lab import TaskCfg; print(TaskCfg.__name__)" +uv run python -c "import torch; print(torch.__version__, torch.version.cuda)" uv run python -c "import genesis; print(genesis.__version__)" -uv run pytest -uv run ruff check -uv run pyright ``` -验证当前 PyTorch build: +## 安装示例扩展 + +核心包是框架;任务由扩展注册。 ```bash -uv run python -c "import torch; print(torch.__version__, torch.version.cuda)" +uv pip install -e examples/inverted_pendulum +uv pip install -e examples/genelab_examples +uv run genelab list tasks +``` + +只有需要更大的人形机器人示例时再安装 Unitree: + +```bash +uv pip install -e examples/unitree ``` -## See also +## 另见 -- [快速开始](quickstart.md) +- [教程](../tutorial.md) +- [调试常见问题](../best-practices/debugging.md) +- [构建扩展项目](../best-practices/extension-projects.md) diff --git a/docs/getting-started/quickstart.en.md b/docs/getting-started/quickstart.en.md index cabe4a0a..55ed3e6a 100644 --- a/docs/getting-started/quickstart.en.md +++ b/docs/getting-started/quickstart.en.md @@ -1,11 +1,8 @@ # Quickstart -## 1. Listing registered content +Use this page when the environment is already installed and you want the shortest command path. -The core `genelab` package ships empty registries — robots, environments, and tasks are -contributed by extension packages. The example extension under `examples/genelab_examples/` is -discovered automatically because its `pyproject.toml` declares a `genelab.extensions` entry -point. +## 1. List registered content ```bash uv run genelab list robots @@ -13,129 +10,55 @@ uv run genelab list envs uv run genelab list tasks ``` -An empty registry means no extension has been imported. Install one with `uv pip install`, or -pass `--import MODULE` on the command line. - -## 2. Running a task - -```bash -uv run genelab play -``` - -The CLI resolves `` against the `TASKS` registry, constructs its `TaskCfg`, applies any -command-line overrides, and runs the rollout in the configured Genesis backend. - -The three scene shortcuts: +If a task is missing, load its extension explicitly: ```bash -uv run genelab play --vis # enable visualization -uv run genelab play --steps 500 # cap the episode length -uv run genelab play --gpu 1 # pin to a single GPU +PYTHONPATH=examples/inverted_pendulum/src \ + uv run genelab --import genelab_inverted_pendulum.tasks list tasks ``` -Arbitrary overrides use the dotted `--a.b.c VALUE` syntax — strings are coerced to the type -declared on the target dataclass field: +## 2. Inspect a task ```bash -uv run genelab play \ - --env.simulation.dt 0.005 \ - --env.actions.scale 0.5 +uv run genelab info GeneLab-Inverted-Pendulum-v0 ``` -## 3. Training +Copy override paths from the printed config tree instead of guessing. -When a task ships an RL runner (`rsl_rl` and similar), training is launched with: +## 3. Play ```bash -uv run genelab train -uv run genelab train --gpus 4 # multi-GPU via torchrun -uv run genelab train --checkpoint path/to/model.pt +uv run genelab play GeneLab-Inverted-Pendulum-v0 --steps 64 +uv run genelab play GeneLab-Inverted-Pendulum-v0 --vis --steps 500 +uv run genelab play GeneLab-Inverted-Pendulum-v0 --agent random --steps 128 ``` -!!! warning "Distributed training" - `--gpus N` transparently dispatches through `torchrun` for distributed training; the target - task's runner must be `torchrun`-compatible. - -## 4. Scaffolding a project - -A standalone downstream package is generated with: - -```bash -uv run genelab project new my_robot_project -``` - -This produces `config.py`, `robots.py`, `envs.py`, `tasks.py`, and a `pyproject.toml` that -declares a `genelab.extensions` entry point — the same shape as the bundled example extension. - -## 5. Advanced: end-to-end RL on Unitree G1 { #unitree-g1 } - -The repository ships a production-grade RL example under `examples/unitree/` — two PPO tasks on -the Unitree G1 humanoid (velocity tracking and motion imitation), ported from mjlab and adapted -to Genesis. The following walks through install, training, and checkpoint replay in one -sitting. - -### 5.1 Install the extension - -The Unitree extension pulls in vendored MJCF + meshes (~19 MB). Pick the `torch-*` extra that -matches your hardware — see *Installation* for the full extras table. +## 4. Train ```bash -uv sync --extra torch-cu128 -uv pip install -e examples/unitree - -uv run genelab list tasks -# -> Genelab-Velocity-Flat-Unitree-G1-v0 -# -> Genelab-Tracking-Flat-Unitree-G1-v0 +uv run genelab train GeneLab-Inverted-Pendulum-v0 \ + --num_envs 64 \ + --max_iterations 2 ``` -### 5.2 Velocity tracking PPO - -The velocity-tracking task asks the G1 to follow a commanded body-frame twist. Train, then -replay the resulting checkpoint: +Longer run: ```bash -uv run genelab train Genelab-Velocity-Flat-Unitree-G1-v0 \ - --num-envs 4096 --max-iterations 1500 - -uv run genelab play Genelab-Velocity-Flat-Unitree-G1-v0 \ - --checkpoint logs/rsl_rl/g1_velocity_flat//model_1500.pt +uv run genelab train GeneLab-Inverted-Pendulum-v0 \ + --num_envs 4096 \ + --max_iterations 300 ``` -`--checkpoint` makes `play` route through the RL runner with `--agent trained` by default. - -### 5.3 Motion imitation - -The tracking task imitates a recorded clip per-body (BeyondMimic-style). The default clip -is the LAFAN1 retargeted `dance1_subject2` NPZ, fetched on first use from `genelab-assets` -via `genelab.asset_zoo.unitree_g1_motions.g1_lafan1_dance1_subject2()` and cached under -`.cache/`. No manual download required. +## 5. Scaffold a project ```bash -# Replay the reference clip frame-by-frame (no policy; robot snaps to motion each step). -uv run python -m genelab_unitree.replay_motion - -# Train. -uv run genelab train Genelab-Tracking-Flat-Unitree-G1-v0 \ - --num-envs 4096 --max-iterations 30000 - -# Replay trained policy. -uv run genelab play Genelab-Tracking-Flat-Unitree-G1-v0 \ - --agent trained \ - --checkpoint logs/rsl_rl/g1_tracking_flat//model_30000.pt +uv run genelab project new my_robot_project +uv pip install -e my_robot_project +uv run genelab list tasks ``` -### 5.4 `--agent` modes - -`--agent` selects the policy source for `play`: - -| Value | Policy source | -|-------|--------------| -| `zero` | Constant zero action — basic sanity check; robot resets then falls under gravity. | -| `random` | Uniform-random action sampling — visible perturbation around the reference pose. | -| `trained` | Load from `--checkpoint`. This is the default whenever `--checkpoint` is set. | - ## See also -- [Configs](../concepts/configs.md) -- [Extensions](../concepts/extensions.md) -- [Examples](../examples/overview.md) +- [Tutorial](../tutorial.md) +- [CLI Reference](../reference/cli.md) +- [Run RL Experiments](../best-practices/rl-experiments.md) diff --git a/docs/getting-started/quickstart.zh.md b/docs/getting-started/quickstart.zh.md index 69d581eb..58bc7ba8 100644 --- a/docs/getting-started/quickstart.zh.md +++ b/docs/getting-started/quickstart.zh.md @@ -1,10 +1,8 @@ # 快速开始 -## 1. 列出已注册内容 +环境已经安装好、只想走最短命令路径时,用这一页。 -核心 `genelab` 包自带空注册表 —— 机器人、环境、任务都由扩展包贡献。仓库内 -`examples/genelab_examples/` 通过 `pyproject.toml` 中的 `genelab.extensions` entry point -被自动发现。 +## 1. 列出已注册内容 ```bash uv run genelab list robots @@ -12,126 +10,55 @@ uv run genelab list envs uv run genelab list tasks ``` -注册表为空意味着没有扩展被导入。可通过 `uv pip install` 安装一个,或在命令行加 -`--import MODULE`。 - -## 2. 运行任务 - -```bash -uv run genelab play -``` - -CLI 会在 `TASKS` 注册表中解析 ``,构造其 `TaskCfg`,应用命令行 override,然后在 -Genesis 后端跑 rollout。 - -三个场景短标志: - -```bash -uv run genelab play --vis # 启用可视化 -uv run genelab play --steps 500 # 限制单 episode 步数 -uv run genelab play --gpu 1 # 锁定到单张 GPU -``` - -任意 override 使用 `--a.b.c VALUE` 点路径写法,字符串值按目标 dataclass 字段的类型注解自动 -转换: +如果缺少某个任务,显式加载对应扩展: ```bash -uv run genelab play \ - --env.simulation.dt 0.005 \ - --env.actions.scale 0.5 +PYTHONPATH=examples/inverted_pendulum/src \ + uv run genelab --import genelab_inverted_pendulum.tasks list tasks ``` -## 3. 训练 - -任务自带 RL runner(`rsl_rl` 等)时,以如下方式启动训练: +## 2. 查看任务 ```bash -uv run genelab train -uv run genelab train --gpus 4 # 多 GPU 通过 torchrun 启动 -uv run genelab train --checkpoint path/to/model.pt +uv run genelab info GeneLab-Inverted-Pendulum-v0 ``` -!!! warning "分布式训练" - `--gpus N` 会透明走 `torchrun` 启动分布式训练,前提是任务自身的 runner 支持 `torchrun`。 - -## 4. 新建下游项目 +从打印出的配置树复制 override 路径,不要手猜。 -生成一个独立的下游扩展包: +## 3. Play ```bash -uv run genelab project new my_robot_project +uv run genelab play GeneLab-Inverted-Pendulum-v0 --steps 64 +uv run genelab play GeneLab-Inverted-Pendulum-v0 --vis --steps 500 +uv run genelab play GeneLab-Inverted-Pendulum-v0 --agent random --steps 128 ``` -会生成 `config.py`、`robots.py`、`envs.py`、`tasks.py` 以及一份声明了 `genelab.extensions` -entry point 的 `pyproject.toml` —— 形态与仓库自带示例扩展一致。 - -## 5. 进阶:在 Unitree G1 上跑通完整 RL 流程 { #unitree-g1 } - -仓库在 `examples/unitree/` 下提供一个生产级 RL 示例:两个 Unitree G1 人形机器人的 PPO 任务 -(速度跟踪与动作模仿),从 mjlab 移植并适配到 Genesis。下面给出从安装到训练再到 checkpoint -回放的全部命令,无需跳页阅读。 - -### 5.1 安装扩展 - -unitree 扩展随包附带 MJCF 与网格资源(约 19 MB)。`torch-*` extra 需按硬件挑选 —— -对照表见 *安装*。 +## 4. Train ```bash -uv sync --extra torch-cu128 -uv pip install -e examples/unitree - -uv run genelab list tasks -# -> Genelab-Velocity-Flat-Unitree-G1-v0 -# -> Genelab-Tracking-Flat-Unitree-G1-v0 +uv run genelab train GeneLab-Inverted-Pendulum-v0 \ + --num_envs 64 \ + --max_iterations 2 ``` -### 5.2 速度跟踪 PPO - -速度跟踪任务要求 G1 跟随给定的 body-frame twist。训练后再回放训练好的 checkpoint: +更长训练: ```bash -uv run genelab train Genelab-Velocity-Flat-Unitree-G1-v0 \ - --num-envs 4096 --max-iterations 1500 - -uv run genelab play Genelab-Velocity-Flat-Unitree-G1-v0 \ - --checkpoint logs/rsl_rl/g1_velocity_flat//model_1500.pt +uv run genelab train GeneLab-Inverted-Pendulum-v0 \ + --num_envs 4096 \ + --max_iterations 300 ``` -`--checkpoint` 会让 `play` 走 RL runner,并把 `--agent` 默认设为 `trained`。 - -### 5.3 动作模仿 - -动作模仿任务按 body 跟踪一段录制的动作片段(BeyondMimic 风格)。默认 clip 是 LAFAN1 -retargeted 的 `dance1_subject2` NPZ,首次使用时由 -`genelab.asset_zoo.unitree_g1_motions.g1_lafan1_dance1_subject2()` 从 `genelab-assets` -拉取并缓存到 `.cache/`,无需手动下载。 +## 5. 生成项目 ```bash -# 逐帧回放参考 clip (不接策略;机器人每步贴到 motion 当前帧)。 -uv run python -m genelab_unitree.replay_motion - -# 训练。 -uv run genelab train Genelab-Tracking-Flat-Unitree-G1-v0 \ - --num-envs 4096 --max-iterations 30000 - -# 回放训练好的策略。 -uv run genelab play Genelab-Tracking-Flat-Unitree-G1-v0 \ - --agent trained \ - --checkpoint logs/rsl_rl/g1_tracking_flat//model_30000.pt +uv run genelab project new my_robot_project +uv pip install -e my_robot_project +uv run genelab list tasks ``` -### 5.4 `--agent` 三种模式 - -`--agent` 决定 `play` 的策略来源: - -| 取值 | 策略来源 | -|------|---------| -| `zero` | 恒零动作 —— 基本健康检查;机器人 reset 后在重力下倒掉。 | -| `random` | 均匀随机动作 —— 在参考姿态附近显示可见扰动。 | -| `trained` | 从 `--checkpoint` 加载。设置 `--checkpoint` 时即为默认。 | - -## See also +## 另见 -- [配置系统](../concepts/configs.md) -- [扩展加载](../concepts/extensions.md) -- [示例](../examples/overview.md) +- [教程](../tutorial.md) +- [CLI 参考](../reference/cli.md) +- [运行 RL 实验](../best-practices/rl-experiments.md) diff --git a/docs/index.en.md b/docs/index.en.md index 452b6227..20352bc5 100644 --- a/docs/index.en.md +++ b/docs/index.en.md @@ -17,7 +17,7 @@ package layout and CLI for downstream robotics projects.

-[Get started](getting-started/installation.md){ .md-button .md-button--primary } +[Start the tutorial](tutorial.md){ .md-button .md-button--primary } [View on GitHub](https://github.com/KraHsu/GeneLab){ .md-button }
@@ -45,21 +45,21 @@ package layout and CLI for downstream robotics projects.
-- 01 :material-download:{ .lg .middle } **Install** +- 01 :material-school-outline:{ .lg .middle } **Tutorial** --- - Set up `uv`, pick a `torch-*` extra, and verify the CLI. + Build the first robot experiment, then continue to Unitree G1. - [Installation →](getting-started/installation.md) + [Tutorial →](tutorial.md) -- 02 :material-rocket-launch-outline:{ .lg .middle } **Run** +- 02 :material-download:{ .lg .middle } **Install** --- - List registered tasks and play one in a Genesis viewer. + Set up `uv`, pick a `torch-*` extra, and verify the CLI. - [Quickstart →](getting-started/quickstart.md) + [Installation →](getting-started/installation.md) - 03 :material-console-line:{ .lg .middle } **CLI** @@ -79,13 +79,13 @@ package layout and CLI for downstream robotics projects.
-## Modules +## Abilities -
    -
  • genelab.registryRegistries, registration helpers, extension loading.
  • -
  • genelab.configsReusable dataclass configs (ManagerBasedEnvCfg, TaskCfg).
  • -
  • genelab.labPublic facade for registry + manager-based environment primitives.
  • -
  • genelab.cliTyper + Rich dispatcher; play / train / info / project new.
  • -
  • genelab.envs / robots / tasksCore registry helper namespaces.
  • -
  • genelab.actuator / scene / sensor / terrainsExtension namespaces for robotics research code.
  • +
      +
    • DiscoverRegister and inspect robots, environments, and tasks from installed or explicit extensions.
    • +
    • ComposeBuild Genesis scenes from robot assets, rigid objects, terrains, sensors, and recordings.
    • +
    • Shape MDPsDefine actions, commands, observations, rewards, terminations, events, curricula, and metrics as named terms.
    • +
    • RunUse one CLI for headless rollouts, viewer playback, config overrides, and task introspection.
    • +
    • TrainLaunch RSL-RL PPO runs, checkpoint replay, profiler traces, and multi-GPU training.
    • +
    • ExtendKeep downstream robot projects in independent Python packages with entry-point discovery.
    diff --git a/docs/index.zh.md b/docs/index.zh.md index c90d7740..9210c908 100644 --- a/docs/index.zh.md +++ b/docs/index.zh.md @@ -17,7 +17,7 @@ hide:

    -[开始使用](getting-started/installation.md){ .md-button .md-button--primary } +[开始教程](tutorial.md){ .md-button .md-button--primary } [查看 GitHub](https://github.com/KraHsu/GeneLab){ .md-button }
    @@ -45,21 +45,21 @@ hide:
    -- 01 :material-download:{ .lg .middle } **安装** +- 01 :material-school-outline:{ .lg .middle } **教程** --- - 准备 `uv`,挑选一个 `torch-*` extra,并验证 CLI。 + 构建第一个机器人实验,然后继续到 Unitree G1。 - [安装 →](getting-started/installation.md) + [教程 →](tutorial.md) -- 02 :material-rocket-launch-outline:{ .lg .middle } **运行** +- 02 :material-download:{ .lg .middle } **安装** --- - 列出已注册任务,并在 Genesis viewer 中 play 一个。 + 准备 `uv`,挑选一个 `torch-*` extra,并验证 CLI。 - [快速开始 →](getting-started/quickstart.md) + [安装 →](getting-started/installation.md) - 03 :material-console-line:{ .lg .middle } **CLI** @@ -79,13 +79,13 @@ hide:
    -## 模块速览 +## 能力速览 -
      -
    • genelab.registry注册表、注册 helper、扩展加载。
    • -
    • genelab.configs可复用的 dataclass 配置(ManagerBasedEnvCfg、TaskCfg)。
    • -
    • genelab.lab注册表与 manager-based 环境原语的公共 facade。
    • -
    • genelab.cliTyper + Rich 调度器;play / train / info / project new。
    • -
    • genelab.envs / robots / tasks核心注册 helper 命名空间。
    • -
    • genelab.actuator / scene / sensor / terrains面向机器人研究代码的扩展命名空间。
    • +
        +
      • 发现从已安装或显式导入的扩展中注册并检查机器人、环境和任务。
      • +
      • 组合用机器人资产、刚体、地形、传感器和录制配置构建 Genesis scene。
      • +
      • 塑造 MDP把 action、command、observation、reward、termination、event、curriculum、metric 定义成命名 term。
      • +
      • 运行用统一 CLI 做 headless rollout、viewer 回放、配置 override 和任务检查。
      • +
      • 训练启动 RSL-RL PPO、checkpoint 回放、profiler trace 和多 GPU 训练。
      • +
      • 扩展把下游机器人项目放在独立 Python 包中,并通过 entry point 自动发现。
      diff --git a/docs/reference/cli.en.md b/docs/reference/cli.en.md new file mode 100644 index 00000000..08b82eff --- /dev/null +++ b/docs/reference/cli.en.md @@ -0,0 +1,76 @@ +# CLI Reference + +GeneLab exposes one console script: + +```bash +uv run genelab [global options] [command options] +``` + +## Global options + +| Option | Description | +|---|---| +| `--version` | Print GeneLab version and exit. | +| `--import MODULE` | Import an extension module before dispatch. Repeatable. | +| `--no-entry-points` | Skip installed `genelab.extensions` entry points. Useful for reproducible debugging with explicit imports. | +| `--help` | Show help. | + +## Commands + +| Command | Purpose | +|---|---| +| `cache` | Create project-local cache directories. | +| `list robots` | List registered robots. | +| `list envs` | List registered environments. | +| `list tasks` | List registered tasks. | +| `info NAME` | Show detail for a registered task, env, or robot. | +| `play TASK ...` | Run a registered task. | +| `train TASK ...` | Train a task with a supported runner. | +| `prof open [DIR]` | Open TensorBoard for profiler traces. | +| `project new NAME` | Scaffold an external GeneLab extension project. | + +## Runtime flags + +These flags are accepted after the task id for `play` and `train`. + +| Flag | Description | +|---|---| +| `--vis`, `-v` | Enable the Genesis viewer. | +| `--gpu` | Use the Genesis GPU backend. | +| `--steps N` | In `play`, rollout steps. In `train`, short form for `--max_iterations N`. | +| `--dt X` | Override simulator timestep. | +| `--a.b.c VALUE` | Apply a dotted config override. | + +## Runner flags + +| Flag | Command | Description | +|---|---|---| +| `--num_envs N` | play/train | Total number of envs. In distributed train, divided by `--gpus`. | +| `--num_envs_per_gpu N` | play/train | Per-rank env count. Mutually exclusive with `--num_envs`. | +| `--agent zero|random|trained` | play | Select policy source. | +| `--checkpoint PATH` | play/train | Load a checkpoint. In play, defaults agent to `trained`. | +| `--seed N` | train | Override env and agent seed. | +| `--log_dir PATH` | train | Use a resolved log directory. | +| `--max_iterations N` | train | Override PPO iteration count. | +| `--gpus N` | train | Relaunch under `torchrun` with `N` local ranks. | + +## Profiler flags + +| Flag | Environment fallback | Default | +|---|---|---| +| `--prof` | `GENELAB_PROFILE=1` | off | +| `--prof-out PATH` | `GENELAB_PROFILE_OUT` | `logs/torch_profile` | +| `--prof-wait N` | `GENELAB_PROFILE_WAIT` | `10` | +| `--prof-warmup N` | `GENELAB_PROFILE_WARMUP` | `5` | +| `--prof-active N` | `GENELAB_PROFILE_ACTIVE` | `10` | +| `--prof-repeat N` | `GENELAB_PROFILE_REPEAT` | `2` | +| `--prof-record-shapes` | `GENELAB_PROFILE_RECORD_SHAPES=1` | off | +| `--prof-with-stack` | `GENELAB_PROFILE_WITH_STACK=1` | off | + +## Completion and interactive recovery + +Typer provides `--install-completion` and `--show-completion`. Completion loads installed entry +points but cannot see temporary `--import MODULE` extensions. + +When stdin is a TTY, GeneLab can prompt for missing task ids, unknown names, invalid `--agent` +values, and unknown override paths. In scripts and CI, errors are raised directly. diff --git a/docs/reference/cli.zh.md b/docs/reference/cli.zh.md new file mode 100644 index 00000000..a570234c --- /dev/null +++ b/docs/reference/cli.zh.md @@ -0,0 +1,76 @@ +# CLI 参考 + +GeneLab 暴露一个命令行脚本: + +```bash +uv run genelab [全局选项] <命令> [命令选项] +``` + +## 全局选项 + +| 选项 | 说明 | +|---|---| +| `--version` | 打印 GeneLab 版本并退出。 | +| `--import MODULE` | 派发命令前导入扩展模块,可重复。 | +| `--no-entry-points` | 跳过已安装的 `genelab.extensions` entry point。适合做完全显式的调试。 | +| `--help` | 显示帮助。 | + +## 命令 + +| 命令 | 用途 | +|---|---| +| `cache` | 创建项目本地缓存目录。 | +| `list robots` | 列出已注册机器人。 | +| `list envs` | 列出已注册环境。 | +| `list tasks` | 列出已注册任务。 | +| `info NAME` | 查看已注册 task、env 或 robot 的详情。 | +| `play TASK ...` | 运行已注册任务。 | +| `train TASK ...` | 用支持的 runner 训练任务。 | +| `prof open [DIR]` | 为 profiler trace 打开 TensorBoard。 | +| `project new NAME` | 生成外部 GeneLab 扩展项目骨架。 | + +## 运行时标志 + +这些标志放在 `play` 或 `train` 的 task id 后面。 + +| 标志 | 说明 | +|---|---| +| `--vis`、`-v` | 启用 Genesis viewer。 | +| `--gpu` | 使用 Genesis GPU 后端。 | +| `--steps N` | 在 `play` 中表示 rollout 步数;在 `train` 中是 `--max_iterations N` 的短写。 | +| `--dt X` | 覆盖仿真时间步。 | +| `--a.b.c VALUE` | 应用 dotted config override。 | + +## Runner 标志 + +| 标志 | 命令 | 说明 | +|---|---|---| +| `--num_envs N` | play/train | env 总数。分布式 train 中会除以 `--gpus`。 | +| `--num_envs_per_gpu N` | play/train | 每个 rank 的 env 数。与 `--num_envs` 互斥。 | +| `--agent zero|random|trained` | play | 选择策略来源。 | +| `--checkpoint PATH` | play/train | 加载 checkpoint。play 中会默认把 agent 设为 `trained`。 | +| `--seed N` | train | 覆盖 env 和 agent seed。 | +| `--log_dir PATH` | train | 使用已解析日志目录。 | +| `--max_iterations N` | train | 覆盖 PPO iteration 数。 | +| `--gpus N` | train | 用 `torchrun` 重新启动为 `N` 个本地 rank。 | + +## Profiler 标志 + +| 标志 | 环境变量兜底 | 默认 | +|---|---|---| +| `--prof` | `GENELAB_PROFILE=1` | 关闭 | +| `--prof-out PATH` | `GENELAB_PROFILE_OUT` | `logs/torch_profile` | +| `--prof-wait N` | `GENELAB_PROFILE_WAIT` | `10` | +| `--prof-warmup N` | `GENELAB_PROFILE_WARMUP` | `5` | +| `--prof-active N` | `GENELAB_PROFILE_ACTIVE` | `10` | +| `--prof-repeat N` | `GENELAB_PROFILE_REPEAT` | `2` | +| `--prof-record-shapes` | `GENELAB_PROFILE_RECORD_SHAPES=1` | 关闭 | +| `--prof-with-stack` | `GENELAB_PROFILE_WITH_STACK=1` | 关闭 | + +## 补全与交互式恢复 + +Typer 提供 `--install-completion` 和 `--show-completion`。补全会加载已安装 entry point, +但看不到临时 `--import MODULE` 扩展。 + +stdin 是 TTY 时,GeneLab 可对缺失 task id、未知名字、非法 `--agent`、未知 override 路径 +弹出选择器。脚本和 CI 中会直接抛出错误。 diff --git a/docs/reference/configuration.en.md b/docs/reference/configuration.en.md new file mode 100644 index 00000000..6915c21f --- /dev/null +++ b/docs/reference/configuration.en.md @@ -0,0 +1,74 @@ +# Configuration Reference + +GeneLab configuration is plain Python dataclasses. The CLI mutates these dataclasses before runtime +through dotted overrides; task code can construct or clone them directly. + +## Top-level task shape + +| Field | Type | Meaning | +|---|---|---| +| `TaskCfg.name` | `str` | Stable task id registered in `TASKS`. | +| `TaskCfg.env_name` | `str` | Name of the registered environment. | +| `TaskCfg.robot_name` | `str` | Name of the registered robot. | +| `TaskCfg.env` | `object` | Training/default environment config. Usually a `ManagerBasedRlEnvCfg` subclass. | +| `TaskCfg.play_env` | `object | None` | Optional play-mode config. `genelab play` prefers it when present. | +| `TaskCfg.agent` | `object | None` | Runner config. RSL-RL tasks use `RslRlOnPolicyRunnerCfg`. | +| `TaskCfg.trainable` | `bool` | Informational flag shown by registry views. | + +## Runtime configuration + +| Dataclass | Important fields | +|---|---| +| `SimulationCfg` | `vis`, `gpu`, `steps`, `dt`, `substeps`, `num_envs` | +| `InteractiveSceneCfg` | `env_spacing`, `sensors`, `mouse_interaction`, `entities`, `terrain`, `batch_render`, `recordings` | +| `ManagerBasedEnvCfg` | `simulation`, `scene`, and lightweight enabled flags for core manager groups | +| `ManagerBasedRlEnvCfg` | `decimation`, `episode_length_s`, `device`, `seed`, `robot`, `actions_cfg`, `observations_cfg`, `rewards_cfg`, `terminations_cfg`, `commands_cfg`, `events_cfg`, `curriculum_cfg`, `metrics_cfg`, `bridges_cfg` | + +## Override grammar + +Any `--a.b.c VALUE` after a `play` or `train` task id becomes an override. Hyphens in option names +are converted to underscores. + +```bash +uv run genelab play GeneLab-Inverted-Pendulum-v0 \ + --env.simulation.dt 0.005 \ + --env.rewards_cfg.pole_upright.weight 4.0 +``` + +The path is resolved against `TaskCfg`, so `env.*` points to `task.cfg.env` and `play_env.*` points +to `task.cfg.play_env`. + +## Shortcut aliases + +| CLI flag | Play target | Train target | +|---|---|---| +| `--vis` / `-v` | `play_env.simulation.vis=true` when `play_env` exists, else `env.simulation.vis=true` | `env.simulation.vis=true` | +| `--gpu` | `play_env.simulation.gpu=true` when `play_env` exists, else `env.simulation.gpu=true` | `env.simulation.gpu=true` | +| `--steps N` | `play_env.simulation.steps=N` when `play_env` exists, else `env.simulation.steps=N` | `--max_iterations N` | +| `--dt X` | `play_env.simulation.dt=X` when `play_env` exists, else `env.simulation.dt=X` | `env.simulation.dt=X` | + +## Type coercion + +`apply_overrides` reads dataclass annotations when available and converts strings to the target +type. + +| Target type | Accepted value | +|---|---| +| `bool` | `true`, `false`, `1`, `0`, `yes`, `no`, `on`, `off` | +| `int` | Decimal integer | +| `float` | Python float literal | +| `Path` | `pathlib.Path(value)` | +| `list[T]` | Comma-separated values coerced as `T` | +| `tuple[T, ...]` | Comma-separated values coerced as `T` | +| `None` | `none` or `null` | +| Other | String fallback | + +Unknown paths and invalid conversions fail before the simulator starts. + +## Discovering paths + +```bash +uv run genelab info GeneLab-Inverted-Pendulum-v0 +``` + +Copy paths from `Overridable cfg paths`. This is safer than guessing nested term names by hand. diff --git a/docs/reference/configuration.zh.md b/docs/reference/configuration.zh.md new file mode 100644 index 00000000..ea1d957c --- /dev/null +++ b/docs/reference/configuration.zh.md @@ -0,0 +1,73 @@ +# 配置参考 + +GeneLab 配置是普通 Python dataclass。CLI 在运行前通过 dotted override 修改这些 dataclass; +任务代码也可以直接构造或复制它们。 + +## 顶层任务结构 + +| 字段 | 类型 | 含义 | +|---|---|---| +| `TaskCfg.name` | `str` | 注册到 `TASKS` 的稳定 task id。 | +| `TaskCfg.env_name` | `str` | 已注册环境名。 | +| `TaskCfg.robot_name` | `str` | 已注册机器人名。 | +| `TaskCfg.env` | `object` | 默认/训练环境配置,通常是 `ManagerBasedRlEnvCfg` 子类。 | +| `TaskCfg.play_env` | `object | None` | 可选的 play 模式配置。`genelab play` 优先使用它。 | +| `TaskCfg.agent` | `object | None` | runner 配置。RSL-RL 任务使用 `RslRlOnPolicyRunnerCfg`。 | +| `TaskCfg.trainable` | `bool` | 注册表视图展示用标志。 | + +## 运行时配置 + +| Dataclass | 重要字段 | +|---|---| +| `SimulationCfg` | `vis`、`gpu`、`steps`、`dt`、`substeps`、`num_envs` | +| `InteractiveSceneCfg` | `env_spacing`、`sensors`、`mouse_interaction`、`entities`、`terrain`、`batch_render`、`recordings` | +| `ManagerBasedEnvCfg` | `simulation`、`scene` 和轻量 manager enabled flag | +| `ManagerBasedRlEnvCfg` | `decimation`、`episode_length_s`、`device`、`seed`、`robot`、`actions_cfg`、`observations_cfg`、`rewards_cfg`、`terminations_cfg`、`commands_cfg`、`events_cfg`、`curriculum_cfg`、`metrics_cfg`、`bridges_cfg` | + +## Override 语法 + +`play` 或 `train` 的 task id 后面,任何 `--a.b.c VALUE` 都会变成 override。选项名里的 +连字符会转换成下划线。 + +```bash +uv run genelab play GeneLab-Inverted-Pendulum-v0 \ + --env.simulation.dt 0.005 \ + --env.rewards_cfg.pole_upright.weight 4.0 +``` + +路径从 `TaskCfg` 开始解析,因此 `env.*` 指向 `task.cfg.env`,`play_env.*` 指向 +`task.cfg.play_env`。 + +## 短标志别名 + +| CLI 标志 | Play 目标 | Train 目标 | +|---|---|---| +| `--vis` / `-v` | 有 `play_env` 时为 `play_env.simulation.vis=true`,否则为 `env.simulation.vis=true` | `env.simulation.vis=true` | +| `--gpu` | 有 `play_env` 时为 `play_env.simulation.gpu=true`,否则为 `env.simulation.gpu=true` | `env.simulation.gpu=true` | +| `--steps N` | 有 `play_env` 时为 `play_env.simulation.steps=N`,否则为 `env.simulation.steps=N` | `--max_iterations N` | +| `--dt X` | 有 `play_env` 时为 `play_env.simulation.dt=X`,否则为 `env.simulation.dt=X` | `env.simulation.dt=X` | + +## 类型转换 + +`apply_overrides` 会读取 dataclass 注解,并把字符串转换到目标类型。 + +| 目标类型 | 接受值 | +|---|---| +| `bool` | `true`、`false`、`1`、`0`、`yes`、`no`、`on`、`off` | +| `int` | 十进制整数 | +| `float` | Python float 字面量 | +| `Path` | `pathlib.Path(value)` | +| `list[T]` | 逗号分隔,并按 `T` 转换 | +| `tuple[T, ...]` | 逗号分隔,并按 `T` 转换 | +| `None` | `none` 或 `null` | +| 其他 | 字符串兜底 | + +未知路径和无法转换的值会在仿真启动前报错。 + +## 发现可覆盖路径 + +```bash +uv run genelab info GeneLab-Inverted-Pendulum-v0 +``` + +从 `Overridable cfg paths` 里复制路径,比手写嵌套 term 名更可靠。 diff --git a/docs/reference/module-map.en.md b/docs/reference/module-map.en.md new file mode 100644 index 00000000..78700c41 --- /dev/null +++ b/docs/reference/module-map.en.md @@ -0,0 +1,61 @@ +# Module Map + +This reference maps GeneLab's public modules to the part of the system they own. Use it when you +know what you want to change but need the right import path. + +## Public facade + +| Module | Use it for | Typical imports | +|---|---|---| +| `genelab.lab` | Stable user-facing facade for configs, registries, scene objects, sensors, actuators, terrains, and extension loading. | `TaskCfg`, `ManagerBasedEnvCfg`, `ArticulationCfg`, `SensorCfg`, `register_*` via registries | +| `genelab` | Lightweight package root. It lazily re-exports a few top-level types without importing torch. | `__version__`, `TaskCfg`, `ManagerBasedEnvCfg` | + +Prefer `genelab.lab` in notebooks, downstream tasks, and examples unless a lower-level module is +documented as the owner of the API you need. + +## Discovery and dispatch + +| Module | Responsibility | +|---|---| +| `genelab.registry` | `ROBOTS`, `ENVS`, `TASKS`, `RegistryEntry`, registration helpers, entry-point loading, explicit extension imports. | +| `genelab.cli` | Typer/Rich CLI: `cache`, `list`, `info`, `play`, `train`, `prof`, `project new`. | +| `genelab.cache` | Creates project-local cache directories for Genesis, Quadrants, and Matplotlib. | + +## Configuration and MDP + +| Module | Responsibility | +|---|---| +| `genelab.configs` | Core dataclasses: `SimulationCfg`, `InteractiveSceneCfg`, `ManagerBasedEnvCfg`, `TaskCfg`, and `apply_overrides`. | +| `genelab.envs.manager_based_rl_env` | Genesis-backed `ManagerBasedRlEnv` and `ManagerBasedRlEnvCfg`. | +| `genelab.managers` | Manager and term cfg types for actions, commands, observations, rewards, terminations, events, curricula, and metrics. | +| `genelab.mdp` | Reusable MDP functions for observations, rewards, terminations, events, curricula, metrics, noise, actions, commands, and domain randomization. | + +## Simulation objects + +| Module | Responsibility | +|---|---| +| `genelab.scene` | `InteractiveScene`, the owner of the live Genesis scene. | +| `genelab.entity` | `Articulation`, `RigidObject`, and their config dataclasses. | +| `genelab.actuator` | Implicit PD, ideal PD, and DC motor actuator models. | +| `genelab.sensor` | Base sensor API and built-in body velocity, IMU, camera, contact, ray-cast, frame transformer, terrain height, self-contact, and angular momentum sensors. | +| `genelab.terrains` | Sub-terrain configs, terrain grid generation, and terrain import into Genesis. | +| `genelab.asset_zoo` | Curated robot asset configs and motion asset fetchers. This is bundled content, not the core abstraction boundary. | + +## Training and experiment I/O + +| Module | Responsibility | +|---|---| +| `genelab.rl` | RSL-RL configs, VecEnv wrapper, `train_task`, `play_task`, distributed helpers, and profiler integration. | +| `genelab.recording` | Declarative recording configs for plots, files, and camera video. | +| `genelab.bridges` | Runtime input/control bridges, including keyboard twist and DearPyGui twist controls. | +| `genelab.utils` | Math and asset download helpers used by higher-level modules. | + +## Choosing the right layer + +| Goal | Start here | +|---|---| +| Register a new robot, env, or task | `genelab.registry`, then [Build an Extension Project](../best-practices/extension-projects.md) | +| Add observations, rewards, or terminations | `genelab.managers`, `genelab.mdp`, then [Managers and MDP terms](../concepts/managers.md) | +| Add a sensor | `genelab.sensor`, then [Sensors](../concepts/sensors.md) | +| Change training or replay | `genelab.rl`, then [Run RL Experiments](../best-practices/rl-experiments.md) | +| Look up signatures and defaults | [API Reference](../api/reference.md) | diff --git a/docs/reference/module-map.zh.md b/docs/reference/module-map.zh.md new file mode 100644 index 00000000..0e73eb74 --- /dev/null +++ b/docs/reference/module-map.zh.md @@ -0,0 +1,59 @@ +# 模块地图 + +这是一页查阅型参考,用来把 GeneLab 的公开模块映射到它们负责的系统边界。你知道要改什么、但不确定从哪里导入时,先看这里。 + +## 公共 facade + +| 模块 | 用途 | 常见导入 | +|---|---|---| +| `genelab.lab` | 稳定的用户入口,汇总配置、注册表、scene 对象、传感器、执行器、地形和扩展加载。 | `TaskCfg`、`ManagerBasedEnvCfg`、`ArticulationCfg`、`SensorCfg` | +| `genelab` | 轻量包根。懒加载少量顶层类型,避免导入包根时立刻拉起 torch。 | `__version__`、`TaskCfg`、`ManagerBasedEnvCfg` | + +下游任务、notebook 和示例优先从 `genelab.lab` 导入;只有需要明确的低层能力时再进入对应模块。 + +## 发现与调度 + +| 模块 | 职责 | +|---|---| +| `genelab.registry` | `ROBOTS`、`ENVS`、`TASKS`、`RegistryEntry`、注册 helper、entry point 加载、显式扩展导入。 | +| `genelab.cli` | Typer/Rich 命令行:`cache`、`list`、`info`、`play`、`train`、`prof`、`project new`。 | +| `genelab.cache` | 创建 Genesis、Quadrants、Matplotlib 使用的项目本地缓存目录。 | + +## 配置与 MDP + +| 模块 | 职责 | +|---|---| +| `genelab.configs` | 核心 dataclass:`SimulationCfg`、`InteractiveSceneCfg`、`ManagerBasedEnvCfg`、`TaskCfg` 和 `apply_overrides`。 | +| `genelab.envs.manager_based_rl_env` | Genesis 后端的 `ManagerBasedRlEnv` 与 `ManagerBasedRlEnvCfg`。 | +| `genelab.managers` | action、command、observation、reward、termination、event、curriculum、metric 的 manager 与 term cfg。 | +| `genelab.mdp` | 可复用的 observation、reward、termination、event、curriculum、metric、noise、action、command、domain randomization 函数。 | + +## 仿真对象 + +| 模块 | 职责 | +|---|---| +| `genelab.scene` | `InteractiveScene`,持有 live Genesis scene。 | +| `genelab.entity` | `Articulation`、`RigidObject` 及其配置。 | +| `genelab.actuator` | 隐式 PD、理想 PD、DC motor 执行器模型。 | +| `genelab.sensor` | 传感器基类与 body velocity、IMU、camera、contact、ray-cast、frame transformer、terrain height、self-contact、angular momentum 传感器。 | +| `genelab.terrains` | 子地形配置、地形网格生成、导入 Genesis。 | +| `genelab.asset_zoo` | 预置机器人资产配置与 motion 资产下载 helper。它是随包内容,不是核心抽象边界。 | + +## 训练与实验 I/O + +| 模块 | 职责 | +|---|---| +| `genelab.rl` | RSL-RL 配置、VecEnv wrapper、`train_task`、`play_task`、分布式 helper、profiler 集成。 | +| `genelab.recording` | 声明式录制配置:实时曲线、文件、相机视频。 | +| `genelab.bridges` | 运行时输入/控制桥,包括键盘 twist 和 DearPyGui twist 控制。 | +| `genelab.utils` | 数学与资产下载 helper。 | + +## 选层规则 + +| 目标 | 起点 | +|---|---| +| 注册新机器人、环境、任务 | `genelab.registry`,再看 [构建扩展项目](../best-practices/extension-projects.md) | +| 增加 observation、reward、termination | `genelab.managers`、`genelab.mdp`,再看 [Manager 与 MDP term](../concepts/managers.md) | +| 增加传感器 | `genelab.sensor`,再看 [传感器](../concepts/sensors.md) | +| 修改训练或回放 | `genelab.rl`,再看 [运行 RL 实验](../best-practices/rl-experiments.md) | +| 查签名与默认值 | [API 参考](../api/reference.md) | diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 6ef54baa..003bcaa8 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -447,8 +447,8 @@ letter-spacing: 0.02em; } -/* Modules at a glance — 2-column technical layout */ -.gl-modules { +/* Abilities at a glance — 2-column technical layout */ +.gl-abilities { display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 0.4rem 1.6rem; @@ -457,7 +457,7 @@ list-style: none; } -.gl-modules li { +.gl-abilities li { display: grid; grid-template-columns: minmax(8.5rem, max-content) 1fr; gap: 1rem; @@ -468,7 +468,7 @@ line-height: 1.55; } -.gl-modules li code { +.gl-abilities li code { font-size: 0.78rem; background: transparent; padding: 0; @@ -476,10 +476,10 @@ white-space: nowrap; } -[data-md-color-scheme="slate"] .gl-modules li code { +[data-md-color-scheme="slate"] .gl-abilities li code { color: var(--md-primary-fg-color--light); } -.gl-modules li span { +.gl-abilities li span { color: var(--md-default-fg-color--light); } diff --git a/docs/tutorial.en.md b/docs/tutorial.en.md new file mode 100644 index 00000000..eca24d95 --- /dev/null +++ b/docs/tutorial.en.md @@ -0,0 +1,310 @@ +--- +hide: + - toc +--- + +# Build Your First Robot Experiment with GeneLab + +This tutorial starts from a clean checkout and walks through the smallest useful GeneLab loop: +install the environment, inspect the registries, run the inverted pendulum task, understand how a +task reaches the Genesis backend, override configuration, launch a short training run, and scaffold +your own downstream extension project. It then points to the Unitree G1 tasks as the next step for a +full robot workflow. By the end, you should understand the shape of GeneLab and know where to +continue into detailed module, API, and best-practice documentation. + +The intended reader knows Python and the command line, but is new to GeneLab. You do not need to +understand every internal module before starting; each step has a concrete result to verify. + +## What You Will Build + +You will run the bundled `GeneLab-Inverted-Pendulum-v0` task. It is small, but it covers the core +GeneLab path: + +```text +CLI +└── TASKS registry + └── TaskCfg + ├── env / play_env: ManagerBasedRlEnvCfg + ├── robot: ArticulationCfg + ├── manager terms: actions, observations, rewards, terminations, events + └── agent: RslRlOnPolicyRunnerCfg + └── Genesis-backed ManagerBasedRlEnv +``` + +Larger tasks such as Unitree G1, terrain curricula, sensors, and recording use the same route with a +larger robot, richer manager terms, and a more complete runner configuration. + +## 1. Prepare the Environment + +From the repository root, sync dependencies: + +```bash +uv sync --extra torch-cpu +uv run genelab --version +``` + +If you have an NVIDIA GPU, replace `torch-cpu` with the matching `torch-cu126`, `torch-cu128`, or +`torch-cu130` extra. Pick exactly one `torch-*` extra. + +Expected result: + +```text +genelab 0.1.0 +``` + +Create project-local cache directories used by Genesis and plotting backends: + +```bash +uv run genelab cache +``` + +If dependency sync fails or torch is wrong, read [Installation](getting-started/installation.md). + +## 2. Inspect What GeneLab Knows + +The core `genelab` package provides the framework. Robots, environments, and tasks are registered by +the asset zoo or extension packages. List the three registries: + +```bash +uv run genelab list robots +uv run genelab list envs +uv run genelab list tasks +``` + +You should see bundled asset-zoo robots and any example tasks discovered through entry points. If +the task list is empty, explicitly import the inverted pendulum extension: + +```bash +uv run genelab --import genelab_inverted_pendulum.tasks list tasks +``` + +The registries are the first architecture boundary: + +| Registry | Contents | Typical consumer | +|---|---|---| +| `ROBOTS` | Robot or asset config factories | Env configs, CLI `list robots` | +| `ENVS` | Environment factories | CLI `list envs`, downstream projects | +| `TASKS` | Task factories carrying env, play env, and agent cfg | `genelab play`, `genelab train` | + +## 3. Run the First Task + +Start with a short rollout to verify construction and stepping: + +```bash +uv run genelab play GeneLab-Inverted-Pendulum-v0 --steps 32 +``` + +On a local desktop, open the Genesis viewer: + +```bash +uv run genelab play GeneLab-Inverted-Pendulum-v0 --vis --steps 500 +``` + +This step follows this path: + +1. The CLI loads entry-point extensions and explicit `--import` modules. +2. The CLI resolves `GeneLab-Inverted-Pendulum-v0` in `TASKS`. +3. The task returns a `TaskCfg`; when present, `play_env` is preferred for viewer-friendly runs. +4. `ManagerBasedRlEnv` builds the Genesis scene, robot articulation, sensors, and managers. +5. The play loop produces actions and calls `env.step(action)`. + +## 4. Inspect and Override Task Configuration + +Use `info` to inspect task metadata and overridable paths: + +```bash +uv run genelab info GeneLab-Inverted-Pendulum-v0 +``` + +Look for `Overridable cfg paths`. Every CLI `--a.b.c VALUE` is applied to a dataclass field under +`TaskCfg`, with type coercion handled by `apply_overrides`. + +Change the timestep and rollout length: + +```bash +uv run genelab play GeneLab-Inverted-Pendulum-v0 \ + --steps 128 \ + --env.simulation.dt 0.005 +``` + +Common shortcut flags are rewritten to config paths. In `play`, they target `play_env` when the task +defines one; otherwise they target `env`. + +| Shortcut | Play target with `play_env` | Train target | +|---|---|---| +| `--vis` | `play_env.simulation.vis=true` | `env.simulation.vis=true` | +| `--gpu` | `play_env.simulation.gpu=true` | `env.simulation.gpu=true` | +| `--steps N` | `play_env.simulation.steps=N` | `--max_iterations N` | +| `--dt X` | `play_env.simulation.dt=X` | `env.simulation.dt=X` | + +## 5. Read the Task Source Against the Architecture + +The inverted pendulum registration entry point is +`examples/inverted_pendulum/src/genelab_inverted_pendulum/tasks.py`. It does three things: + +```python +register_robot("inverted-pendulum", get_inverted_pendulum_robot_cfg, ...) +register_env("inverted-pendulum-env", lambda: ManagerBasedRlEnv(...), ...) +register_task("GeneLab-Inverted-Pendulum-v0", InvertedPendulumTask, ...) +``` + +The environment config lives in +`examples/inverted_pendulum/src/genelab_inverted_pendulum/single/env_cfg.py`. It shows GeneLab's +manager-based MDP shape: + +```python +actions_cfg={ + "cart": JointPositionActionCfg(...) +} +observations_cfg={ + "policy": ObservationGroupCfg(terms={...}), + "critic": ObservationGroupCfg(terms={...}), +} +rewards_cfg={ + "alive": RewardTermCfg(...), + "pole_upright": RewardTermCfg(...), +} +terminations_cfg={ + "time_out": TerminationTermCfg(...), + "pole_fell": TerminationTermCfg(...), +} +events_cfg={ + "reset_joints": EventTermCfg(mode="reset", ...) +} +``` + +This is the main GeneLab capability: RL environments are assembled from replaceable terms instead +of hiding everything inside one large `step()` function. + +## 6. Launch a Short Training Run + +The inverted pendulum task ships with an RSL-RL runner config. Run a tiny training job to verify the +RL pipeline can construct the env, wrap it as a VecEnv, and write logs/checkpoints: + +```bash +uv run genelab train GeneLab-Inverted-Pendulum-v0 \ + --num_envs 64 \ + --max_iterations 2 +``` + +After it finishes, inspect the log root: + +```bash +ls logs/rsl_rl +``` + +The main process writes: + +```text +params/env.json +params/agent.json +model_*.pt +``` + +Replay a trained policy by passing a checkpoint: + +```bash +uv run genelab play GeneLab-Inverted-Pendulum-v0 \ + --agent trained \ + --checkpoint logs/rsl_rl///model_*.pt +``` + +For environment health checks, use `--agent zero` or `--agent random`. + +## 7. Create Your Own Extension Project + +GeneLab expects real projects to live in their own Python packages rather than under +`src/genelab/`. Generate a scaffold: + +```bash +uv run genelab project new my_robot_project +``` + +The scaffold contains: + +```text +my_robot_project/ +├── pyproject.toml +└── src/my_robot_project/ + ├── config.py + ├── envs.py + ├── robots.py + └── tasks.py +``` + +The key is the entry point in `pyproject.toml`: + +```toml +[project.entry-points."genelab.extensions"] +my_robot_project = "my_robot_project.tasks:register" +``` + +Install it as editable and the CLI discovers it automatically: + +```bash +uv pip install -e my_robot_project +uv run genelab list tasks +``` + +Without installing, import it explicitly: + +```bash +PYTHONPATH=my_robot_project/src \ + uv run genelab --import my_robot_project.tasks list tasks +``` + +## 8. Continue to Unitree G1 + +The Unitree G1 example uses the same architecture with a humanoid robot, larger action and +observation spaces, command sampling, RSL-RL training, and motion imitation. + +Install the extension: + +```bash +uv pip install -e examples/unitree +uv run genelab list tasks +``` + +You should see: + +```text +Genelab-Velocity-Flat-Unitree-G1-v0 +Genelab-Tracking-Flat-Unitree-G1-v0 +``` + +Run a visual smoke test before training: + +```bash +uv run genelab play Genelab-Velocity-Flat-Unitree-G1-v0 --vis --steps 500 +``` + +The two Unitree tasks demonstrate the advanced side of GeneLab: + +| Task | Demonstrates | +|---|---| +| `Genelab-Velocity-Flat-Unitree-G1-v0` | Velocity commands, humanoid action scaling, PPO training, checkpoint replay. | +| `Genelab-Tracking-Flat-Unitree-G1-v0` | Motion-command loading, reference replay, body pose tracking rewards. | + +Read [Unitree G1](examples/unitree-g1.md) for the full training and replay commands. + +## 9. GeneLab Capability Map + +You have now walked through the smallest useful loop and seen where the larger robot example fits. +GeneLab can be understood in four layers: + +| Layer | Modules | Role | +|---|---|---| +| Discovery and dispatch | `genelab.registry`, `genelab.cli` | Register robots/envs/tasks; discover, run, and train from the CLI | +| Configuration and MDP | `genelab.configs`, `genelab.managers`, `genelab.mdp` | Dataclass configs, overrides, action/obs/reward/termination/event/curriculum terms | +| Simulation objects | `genelab.scene`, `genelab.entity`, `genelab.actuator`, `genelab.sensor`, `genelab.terrains` | Genesis scenes, articulations, rigid objects, actuators, sensors, terrains | +| Training and experiments | `genelab.rl`, `genelab.recording`, `genelab.bridges` | RSL-RL training/playback, recording, live plots, keyboard or GUI control | + +## Next Steps + +- For more commands, read [CLI overview](cli/overview.md) and [Play and Train](cli/play-train.md). +- For architecture, read [Module map](reference/module-map.md) and + [Managers and MDP terms](concepts/managers.md). +- To write your own task, read [Task design](best-practices/task-design.md) and + [Extensions](concepts/extensions.md). +- To look up APIs, read [API Reference](api/reference.md). +- For complete demos, read [Examples](examples/overview.md). diff --git a/docs/tutorial.zh.md b/docs/tutorial.zh.md new file mode 100644 index 00000000..e2c7f4e8 --- /dev/null +++ b/docs/tutorial.zh.md @@ -0,0 +1,307 @@ +--- +hide: + - toc +--- + +# 用 GeneLab 构建第一个机器人实验 + +本教程从一个干净 checkout 开始,带你完成 GeneLab 的最小闭环:安装环境、查看注册表、运行 +倒立摆、理解任务如何接入 Genesis 后端、修改配置、启动一次短训练,最后创建一个自己的下游 +扩展项目。教程末尾会把同一套结构延伸到 Unitree G1,作为完整机器人工作流的进阶入口。完成后, +你应该能说清 GeneLab 的整体架构,并知道下一步去哪里查模块、API 和最佳实践。 + +目标读者是熟悉 Python 与命令行、但第一次接触 GeneLab 的机器人或强化学习开发者。教程不要求 +你先理解所有内部模块;每一步都会给出可验证结果。 + +## 你会构建什么 + +你会运行仓库自带的 `GeneLab-Inverted-Pendulum-v0` 任务。这个任务很小,但覆盖了 GeneLab +的核心路径: + +```text +CLI +└── TASKS registry + └── TaskCfg + ├── env / play_env: ManagerBasedRlEnvCfg + ├── robot: ArticulationCfg + ├── manager terms: actions, observations, rewards, terminations, events + └── agent: RslRlOnPolicyRunnerCfg + └── Genesis-backed ManagerBasedRlEnv +``` + +后续复杂任务,例如 Unitree G1、地形课程、传感器和数据录制,本质上是在这条路径上增加更大的 +机器人、更丰富的 manager term,以及更完整的 runner 配置。 + +## 1. 准备环境 + +在仓库根目录同步依赖: + +```bash +uv sync --extra torch-cpu +uv run genelab --version +``` + +如果你有 NVIDIA GPU,把 `torch-cpu` 换成与你驱动匹配的 `torch-cu126`、`torch-cu128` 或 +`torch-cu130`。每次只能选一个 `torch-*` extra。 + +你应该看到类似输出: + +```text +genelab 0.1.0 +``` + +初始化 Genesis 和绘图库需要的项目本地缓存: + +```bash +uv run genelab cache +``` + +如果安装失败或 torch 版本不对,先看 [安装](getting-started/installation.md)。 + +## 2. 查看 GeneLab 当前知道什么 + +GeneLab 的核心包只提供框架。机器人、环境和任务由内置资产库或扩展包注册进来。先列出三张 +注册表: + +```bash +uv run genelab list robots +uv run genelab list envs +uv run genelab list tasks +``` + +你应该能看到内置资产库的机器人,以及已通过 entry point 发现的示例任务。若任务列表为空, +说明示例扩展没有被安装或发现;可以显式导入倒立摆扩展: + +```bash +uv run genelab --import genelab_inverted_pendulum.tasks list tasks +``` + +注册表是 GeneLab 的第一层架构边界: + +| 注册表 | 放什么 | 典型消费者 | +|---|---|---| +| `ROBOTS` | 机器人或资产配置 factory | 环境配置、CLI `list robots` | +| `ENVS` | 可构造环境的 factory | CLI `list envs`、下游项目 | +| `TASKS` | 包含 env、play env、agent 的任务 factory | `genelab play`、`genelab train` | + +## 3. 运行第一个任务 + +先用短 rollout 验证任务可以构造并走完几步: + +```bash +uv run genelab play GeneLab-Inverted-Pendulum-v0 --steps 32 +``` + +如果你在本地桌面环境中运行,可以打开 Genesis viewer: + +```bash +uv run genelab play GeneLab-Inverted-Pendulum-v0 --vis --steps 500 +``` + +这一步经过的路径是: + +1. CLI 加载 entry-point 扩展和显式 `--import` 扩展。 +2. CLI 在 `TASKS` 中查找 `GeneLab-Inverted-Pendulum-v0`。 +3. task 返回 `TaskCfg`,其中 `play_env` 是可视化友好的环境配置。 +4. `ManagerBasedRlEnv` 构造 Genesis scene、机器人 articulation、传感器和各类 manager。 +5. play loop 产生动作并调用 `env.step(action)`。 + +## 4. 看清一个任务的可改配置 + +用 `info` 查看任务元信息和可覆盖路径: + +```bash +uv run genelab info GeneLab-Inverted-Pendulum-v0 +``` + +重点看输出里的 `Overridable cfg paths`。CLI 的每个 `--a.b.c VALUE` 都会落到 +`TaskCfg` 的某个 dataclass 字段上,并由 `apply_overrides` 自动转换类型。 + +试着改仿真步长和 episode 步数: + +```bash +uv run genelab play GeneLab-Inverted-Pendulum-v0 \ + --steps 128 \ + --env.simulation.dt 0.005 +``` + +常用短标志会被改写为配置路径。`play` 中,如果 task 定义了 `play_env`,短标志会优先作用到 +`play_env`;否则才作用到 `env`。 + +| 短标志 | 有 `play_env` 时的 play 目标 | train 目标 | +|---|---|---| +| `--vis` | `play_env.simulation.vis=true` | `env.simulation.vis=true` | +| `--gpu` | `play_env.simulation.gpu=true` | `env.simulation.gpu=true` | +| `--steps N` | `play_env.simulation.steps=N` | `--max_iterations N` | +| `--dt X` | `play_env.simulation.dt=X` | `env.simulation.dt=X` | + +## 5. 打开任务源码并对照架构 + +倒立摆的注册入口在 `examples/inverted_pendulum/src/genelab_inverted_pendulum/tasks.py`。 +它做三件事: + +```python +register_robot("inverted-pendulum", get_inverted_pendulum_robot_cfg, ...) +register_env("inverted-pendulum-env", lambda: ManagerBasedRlEnv(...), ...) +register_task("GeneLab-Inverted-Pendulum-v0", InvertedPendulumTask, ...) +``` + +环境配置在 +`examples/inverted_pendulum/src/genelab_inverted_pendulum/single/env_cfg.py`。这里能看到 +GeneLab 的 manager-based MDP 形状: + +```python +actions_cfg={ + "cart": JointPositionActionCfg(...) +} +observations_cfg={ + "policy": ObservationGroupCfg(terms={...}), + "critic": ObservationGroupCfg(terms={...}), +} +rewards_cfg={ + "alive": RewardTermCfg(...), + "pole_upright": RewardTermCfg(...), +} +terminations_cfg={ + "time_out": TerminationTermCfg(...), + "pole_fell": TerminationTermCfg(...), +} +events_cfg={ + "reset_joints": EventTermCfg(mode="reset", ...) +} +``` + +这一层是 GeneLab 最重要的能力:把强化学习环境拆成可替换的 term,而不是把所有逻辑塞进一个 +巨大的 `step()` 函数。 + +## 6. 启动一次短训练 + +倒立摆任务自带 RSL-RL runner 配置。先跑一个很短的训练,验证 RL 管线可以构造 env、包装 +VecEnv、写日志和 checkpoint: + +```bash +uv run genelab train GeneLab-Inverted-Pendulum-v0 \ + --num_envs 64 \ + --max_iterations 2 +``` + +训练结束后,查看日志目录: + +```bash +ls logs/rsl_rl +``` + +你会看到按实验名和时间戳组织的目录。主进程会写入: + +```text +params/env.json +params/agent.json +model_*.pt +``` + +回放训练好的策略时传入 checkpoint: + +```bash +uv run genelab play GeneLab-Inverted-Pendulum-v0 \ + --agent trained \ + --checkpoint logs/rsl_rl///model_*.pt +``` + +如果只是检查动作边界或环境稳定性,也可以用 `--agent zero` 或 `--agent random`。 + +## 7. 创建自己的扩展项目 + +GeneLab 推荐把真实项目放在独立 Python 包里,而不是直接改 `src/genelab/`。生成一个项目骨架: + +```bash +uv run genelab project new my_robot_project +``` + +骨架会包含: + +```text +my_robot_project/ +├── pyproject.toml +└── src/my_robot_project/ + ├── config.py + ├── envs.py + ├── robots.py + └── tasks.py +``` + +关键是 `pyproject.toml` 里的 entry point: + +```toml +[project.entry-points."genelab.extensions"] +my_robot_project = "my_robot_project.tasks:register" +``` + +安装为 editable 包之后,CLI 会自动发现它: + +```bash +uv pip install -e my_robot_project +uv run genelab list tasks +``` + +如果暂时不安装,也可以显式导入: + +```bash +PYTHONPATH=my_robot_project/src \ + uv run genelab --import my_robot_project.tasks list tasks +``` + +## 8. 继续到 Unitree G1 + +Unitree G1 示例使用同一套架构,但换成了人形机器人、更大的 action/observation 空间、 +命令采样、RSL-RL 训练和动作模仿。 + +安装扩展: + +```bash +uv pip install -e examples/unitree +uv run genelab list tasks +``` + +你应该能看到: + +```text +Genelab-Velocity-Flat-Unitree-G1-v0 +Genelab-Tracking-Flat-Unitree-G1-v0 +``` + +训练前先做可视化 smoke test: + +```bash +uv run genelab play Genelab-Velocity-Flat-Unitree-G1-v0 --vis --steps 500 +``` + +两个 Unitree 任务展示 GeneLab 的进阶能力: + +| Task | 展示能力 | +|---|---| +| `Genelab-Velocity-Flat-Unitree-G1-v0` | 速度命令、人形机器人 action scale、PPO 训练、checkpoint 回放。 | +| `Genelab-Tracking-Flat-Unitree-G1-v0` | motion command 加载、参考动作回放、body pose tracking reward。 | + +完整训练与回放命令见 [Unitree G1](examples/unitree-g1.md)。 + +## 9. GeneLab 能力地图 + +到这里,你已经走完最小闭环,也看到了更大机器人示例的位置。GeneLab 的能力可以按四层理解: + +| 层级 | 模块 | 作用 | +|---|---|---| +| 发现与调度 | `genelab.registry`、`genelab.cli` | 注册机器人、环境、任务;用 CLI 发现、运行、训练 | +| 配置与 MDP | `genelab.configs`、`genelab.managers`、`genelab.mdp` | dataclass 配置、override、action/obs/reward/termination/event/curriculum term | +| 仿真对象 | `genelab.scene`、`genelab.entity`、`genelab.actuator`、`genelab.sensor`、`genelab.terrains` | Genesis scene、articulation、刚体、执行器、传感器、地形 | +| 训练与实验 | `genelab.rl`、`genelab.recording`、`genelab.bridges` | RSL-RL 训练/回放、数据录制、实时绘图、键盘或 GUI 控制 | + +## 下一步 + +按目标继续阅读: + +- 想跑更多命令:看 [CLI 总览](cli/overview.md) 和 [play 与 train](cli/play-train.md)。 +- 想理解架构:看 [模块地图](reference/module-map.md)、[Manager 与 MDP term](concepts/managers.md)。 +- 想写自己的任务:看 [设计任务配置](best-practices/task-design.md) 和 + [扩展加载](concepts/extensions.md)。 +- 想查 API:看 [API 参考](api/reference.md)。 +- 想看完整示例:看 [示例](examples/overview.md)。 diff --git a/mkdocs.yml b/mkdocs.yml index 65199a21..83b625cc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,6 +2,7 @@ site_name: GeneLab site_description: Isaac Lab-inspired API for RL and robotics research powered by Genesis. site_url: https://krahsu.github.io/GeneLab/ site_author: GeneLab contributors +dev_addr: 127.0.0.1:8001 repo_name: KraHsu/GeneLab repo_url: https://github.com/KraHsu/GeneLab @@ -71,6 +72,8 @@ plugins: lang: - en - zh + - autorefs: + resolve_closest: true - include-markdown - i18n: docs_structure: suffix @@ -87,9 +90,19 @@ plugins: site_name: GeneLab nav_translations: Home: 首页 + Tutorial: 教程 Getting Started: 快速上手 Installation: 安装 Quickstart: 快速开始 + How-to Guides: 最佳实践 + Design a Task: 设计任务 + Build an Extension Project: 构建扩展项目 + Run RL Experiments: 运行 RL 实验 + Debug Common Failures: 调试常见问题 + Reference: 参考 + Module Map: 模块地图 + Configuration Reference: 配置参考 + CLI Reference: CLI 参考 CLI: 命令行 Overview: 总览 'Discovery: list and info': '发现:list 与 info' @@ -162,9 +175,20 @@ markdown_extensions: nav: - Home: index.md + - Tutorial: tutorial.md - Getting Started: - Installation: getting-started/installation.md - Quickstart: getting-started/quickstart.md + - How-to Guides: + - Design a Task: best-practices/task-design.md + - Build an Extension Project: best-practices/extension-projects.md + - Run RL Experiments: best-practices/rl-experiments.md + - Debug Common Failures: best-practices/debugging.md + - Reference: + - Module Map: reference/module-map.md + - Configuration Reference: reference/configuration.md + - CLI Reference: reference/cli.md + - API Reference: api/reference.md - CLI: - Overview: cli/overview.md - 'Discovery: list and info': cli/list-info.md @@ -191,5 +215,4 @@ nav: - Showcase: examples/showcase.md - "Rubik's Cube": examples/rubiks-cube.md - Wuji Hand: examples/wuji-hand.md - - API Reference: api/reference.md - Contributing: contributing.md