Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/cli/play-train.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,29 @@ uv run genelab train TASK_ID --gpus 4 --num_envs 4096
`--num_envs` is total across ranks and must divide evenly by `--gpus`. Use
`--num_envs_per_gpu` for per-rank semantics.

## RL backends

The training backend is chosen automatically from the type of the task's agent
config — no flag required:

| Agent config | Backend | Algorithms |
|---|---|---|
| `RslRlOnPolicyRunnerCfg` | `rsl_rl` (default) | PPO |
| `SkrlAgentCfg` | `skrl` | PPO, A2C, SAC, TD3, DDPG |

The [skrl](https://skrl.readthedocs.io) backend is optional — install it with the
`skrl` extra (`uv sync` already includes it in this checkout; downstream users run
`pip install genelab[skrl]`). Pick the algorithm via `SkrlAgentCfg.algorithm`.

skrl trains in environment **timesteps** rather than learning iterations, so
`--max_iterations N` sets the skrl timestep budget for a skrl task. Multi-GPU
(`--gpus`) is supported by the RSL-RL backend only.

```bash
# A task registered with a SkrlAgentCfg routes through the skrl backend.
uv run genelab train GeneLab-Franka-Pick-And-Place-skrl-v0 --num_envs 2048 --max_iterations 12000
```

## Config overrides

Any unknown option after the task id is treated as a dotted config override:
Expand Down
22 changes: 22 additions & 0 deletions docs/cli/play-train.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,28 @@ uv run genelab train TASK_ID --gpus 4 --num_envs 4096
`--num_envs` 表示所有 rank 的总数,必须能被 `--gpus` 整除。每 rank 语义用
`--num_envs_per_gpu`。

## RL 后端

训练后端由 task 的 agent 配置类型自动选择,无需任何 flag:

| Agent 配置 | 后端 | 算法 |
|---|---|---|
| `RslRlOnPolicyRunnerCfg` | `rsl_rl`(默认) | PPO |
| `SkrlAgentCfg` | `skrl` | PPO、A2C、SAC、TD3、DDPG |

[skrl](https://skrl.readthedocs.io) 后端为可选项——通过 `skrl` extra 安装(本仓库
`uv sync` 已包含;下游用户执行 `pip install genelab[skrl]`)。算法通过
`SkrlAgentCfg.algorithm` 选择。

skrl 以环境 **timestep**(而非 learning iteration)计量训练量,因此对 skrl task,
`--max_iterations N` 设定的是 skrl 的 timestep 预算。多 GPU(`--gpus`)仅
RSL-RL 后端支持。

```bash
# 注册为 SkrlAgentCfg 的 task 会走 skrl 后端。
uv run genelab train GeneLab-Franka-Pick-And-Place-skrl-v0 --num_envs 2048 --max_iterations 12000
```

## 配置 override

task id 后的任何未知选项都会被当作 dotted config override:
Expand Down
29 changes: 22 additions & 7 deletions docs/concepts/rl-runner.en.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
# RL Runner

`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.
`genelab.rl` connects registered tasks to a pluggable RL **backend**. It is deliberately thin:
GeneLab owns task resolution, config mutation, env construction, the bridge lifecycle, logs,
profiling hooks, and distributed launch helpers; the backend owns the learning algorithm.

## Backends

`train_task` / `play_task` are backend-agnostic dispatchers. The backend is chosen from the type of
`TaskCfg.agent`:

| Agent config | Backend | Algorithms |
|---|---|---|
| `RslRlOnPolicyRunnerCfg` | `rsl_rl` (default) | PPO |
| `SkrlAgentCfg` | `skrl` | PPO, A2C, SAC, TD3, DDPG |

Backends live under `genelab.rl.backends` and register themselves by config type;
`select_backend(agent_cfg)` resolves one. Adding another library means adding a `Backend`
(`train` / `play`) plus its agent-config dataclass — no change to the dispatcher or CLI.

## Training flow

```text
TASKS.get(task_id)
└── TaskCfg.env + TaskCfg.agent
└── ManagerBasedRlEnv
└── RslRlVecEnvWrapper
└── rsl_rl OnPolicyRunner.learn()
└── select_backend(agent_cfg).train(TrainContext)
├── rsl_rl: RslRlVecEnvWrapper → OnPolicyRunner.learn()
└── skrl: GenelabSkrlWrapper → SequentialTrainer.train()
```

The main process writes `params/env.json`, `params/agent.json`, TensorBoard events, profiler traces,
and checkpoints.
and checkpoints. RSL-RL logs under `logs/rsl_rl/`, skrl under `logs/skrl/`.

## Playback flow

Expand All @@ -25,7 +40,7 @@ and checkpoints.
|---|---|
| `zero` | Returns zero actions. |
| `random` | Uniform random actions. |
| `trained` | Loads a checkpoint and calls RSL-RL inference policy. |
| `trained` | Loads a checkpoint and calls the backend's inference policy. |

Playback exits cleanly when the Genesis viewer closes or when `max_steps` is reached.

Expand Down
27 changes: 21 additions & 6 deletions docs/concepts/rl-runner.zh.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
# RL runner

`genelab.rl` 把已注册 task 接到 RSL-RL。它刻意保持很薄:GeneLab 负责 task 解析、配置修改、
VecEnv 适配、日志、profiling hook 和分布式启动 helper;RSL-RL 负责学习算法。
`genelab.rl` 把已注册 task 接到可插拔的 RL **后端**。它刻意保持很薄:GeneLab 负责 task 解析、
配置修改、环境构建、bridge 生命周期、日志、profiling hook 和分布式启动 helper;后端负责学习算法。

## 后端

`train_task` / `play_task` 是与后端无关的分发器。后端由 `TaskCfg.agent` 的类型决定:

| Agent 配置 | 后端 | 算法 |
|---|---|---|
| `RslRlOnPolicyRunnerCfg` | `rsl_rl`(默认) | PPO |
| `SkrlAgentCfg` | `skrl` | PPO、A2C、SAC、TD3、DDPG |

后端位于 `genelab.rl.backends`,按配置类型自行注册,`select_backend(agent_cfg)` 负责解析。
接入另一个库只需新增一个 `Backend`(`train` / `play`)及其 agent 配置 dataclass——分发器和
CLI 无需改动。

## 训练流程

```text
TASKS.get(task_id)
└── TaskCfg.env + TaskCfg.agent
└── ManagerBasedRlEnv
└── RslRlVecEnvWrapper
└── rsl_rl OnPolicyRunner.learn()
└── select_backend(agent_cfg).train(TrainContext)
├── rsl_rl: RslRlVecEnvWrapper → OnPolicyRunner.learn()
└── skrl: GenelabSkrlWrapper → SequentialTrainer.train()
```

main process 写入 `params/env.json`、`params/agent.json`、TensorBoard event、profiler trace 和 checkpoint。
main process 写入 `params/env.json`、`params/agent.json`、TensorBoard event、profiler trace 和
checkpoint。RSL-RL 写到 `logs/rsl_rl/`,skrl 写到 `logs/skrl/`。

## 回放流程

Expand All @@ -23,7 +38,7 @@ main process 写入 `params/env.json`、`params/agent.json`、TensorBoard event
|---|---|
| `zero` | 返回零动作。 |
| `random` | 均匀随机动作。 |
| `trained` | 加载 checkpoint,并调用 RSL-RL inference policy。 |
| `trained` | 加载 checkpoint,并调用后端的 inference policy。 |

Genesis viewer 关闭或达到 `max_steps` 时,回放会干净退出。

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""skrl PPO agent config for the Franka pick-and-place task.

Mirrors ``ppo_cfg.py`` (RSL-RL) but drives the skrl backend. skrl trains in
environment timesteps rather than learning iterations: with ``--num-envs 2048``
the default ``timesteps`` below is ~24M env-steps, comparable to the RSL-RL run
(500 iterations x 24 steps).
"""

from genelab.rl import SkrlAgentCfg, SkrlExperimentCfg, SkrlModelCfg


def franka_pick_and_place_skrl_cfg() -> SkrlAgentCfg:
return SkrlAgentCfg(
algorithm="PPO",
seed=42,
timesteps=12_000,
rollouts=24,
learning_rate=3.0e-4,
discount_factor=0.99,
learning_epochs=5,
mini_batches=4,
gae_lambda=0.95,
ratio_clip=0.2,
entropy_loss_scale=0.005,
grad_norm_clip=1.0,
models=SkrlModelCfg(
hidden_dims=(256, 256, 128),
activation="elu",
),
experiment=SkrlExperimentCfg(
experiment_name="franka_pick_and_place_skrl",
logger="tensorboard",
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
FrankaPickAndPlaceRobotCfg,
get_franka_pick_and_place_robot_cfg,
)
from genelab_franka_pick_and_place.skrl_cfg import franka_pick_and_place_skrl_cfg

TASK_ID = "GeneLab-Franka-Pick-And-Place-v0"
CARTESIAN_TASK_ID = "GeneLab-Franka-Pick-And-Place-Cartesian-v0"
SKRL_TASK_ID = "GeneLab-Franka-Pick-And-Place-skrl-v0"
ROBOT_NAME = "franka-pick-and-place"
ENV_NAME = "franka-pick-and-place-env"
CARTESIAN_ENV_NAME = "franka-pick-and-place-cartesian-env"
Expand Down Expand Up @@ -97,6 +99,33 @@ def train(self) -> None:
train_task(self.cfg.name, agent)


class FrankaPickAndPlaceSkrlTask:
"""skrl variant of :class:`FrankaPickAndPlaceTask` — same scene / reward shaping,
trained through the skrl backend (PPO) instead of RSL-RL. The backend is selected
automatically from the ``SkrlAgentCfg`` agent config."""

def __init__(self) -> None:
self.cfg = TaskCfg(
name=SKRL_TASK_ID,
env_name=ENV_NAME,
robot_name=ROBOT_NAME,
env=franka_pick_and_place_env_cfg(play=False),
play_env=franka_pick_and_place_env_cfg(play=True),
agent=franka_pick_and_place_skrl_cfg(),
trainable=True,
)

def play(self) -> None:
from genelab.rl import play_task

play_task(self.cfg.name, checkpoint=None)

def train(self) -> None:
from genelab.rl import train_task

train_task(self.cfg.name, self.cfg.agent)


def register() -> None:
if ROBOT_NAME not in ROBOTS:
register_robot(
Expand Down Expand Up @@ -149,3 +178,18 @@ def register() -> None:
f"genelab train {CARTESIAN_TASK_ID} --num-envs 2048 --max-iterations 500",
],
)
if SKRL_TASK_ID not in TASKS:
register_task(
SKRL_TASK_ID,
FrankaPickAndPlaceSkrlTask,
description=(
"skrl PPO Franka Panda pick-and-place — same task as "
f"{TASK_ID}, trained through the skrl backend instead of RSL-RL."
),
cfg_type=TaskCfg,
examples=[
f"genelab play {SKRL_TASK_ID} --vis",
f"genelab train {SKRL_TASK_ID} --num-envs 16 --max-iterations 2",
f"genelab train {SKRL_TASK_ID} --num-envs 2048 --max-iterations 12000",
],
)
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ rl = [
"gymnasium>=1.0",
"tensorboard>=2.20.0",
]
# Optional second RL backend. ``genelab.rl.backends.skrl`` guards every skrl
# import, so this extra is only needed to train/play tasks with a ``SkrlAgentCfg``.
# Pinned ``<2`` until the skrl 2.0 model API is validated against GeneLab.
skrl = [
"skrl>=1.4.0,<2",
]
# Live plot backends for ``genelab.recording``. PyQt plots use pyqtgraph + PyQt5;
# Matplotlib plots use matplotlib. File-only recordings (NPZ/CSV/Video) don't need
# any of these — they're handled by Genesis's own dependencies (numpy + PyAV).
Expand Down Expand Up @@ -73,6 +79,9 @@ rl = [
"gymnasium>=1.0",
"tensorboard>=2.20.0",
]
skrl = [
"skrl>=1.4.0,<2",
]
recording = [
"pyqtgraph>=0.13",
"PyQt5>=5.15",
Expand Down Expand Up @@ -100,7 +109,7 @@ packages = ["src/genelab"]
[tool.uv]
# These groups install on every `uv sync` / `uv run` in this checkout, so daily
# dev only needs to pick a torch extra (e.g. `uv sync --extra torch-cu126`).
default-groups = ["dev", "rl", "recording", "teleop", "docs"]
default-groups = ["dev", "rl", "skrl", "recording", "teleop", "docs"]
conflicts = [
[
{ extra = "torch-cpu" },
Expand Down
27 changes: 18 additions & 9 deletions src/genelab/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,23 +542,32 @@ def _dispatch_train(
if agent_cfg is None:
task.train()
return
from genelab.rl import RslRlOnPolicyRunnerCfg, train_task
from genelab.rl import select_backend, train_task

if not isinstance(agent_cfg, RslRlOnPolicyRunnerCfg):
raise SystemExit(
f"task agent cfg has unsupported type {type(agent_cfg).__name__}; "
"expected RslRlOnPolicyRunnerCfg"
)
# The backend is chosen by the agent cfg type (RSL-RL, skrl, ...); an
# unregistered type raises a clear error here instead of deep in the runner.
try:
backend = select_backend(agent_cfg)
except ValueError as exc:
raise SystemExit(str(exc)) from exc
task_id = getattr(task_cfg, "name", None)
if not isinstance(task_id, str):
raise SystemExit("task config is missing 'name'; cannot route through RL train helper")

gpus_raw = runner_args.pop("gpus", None)
gpus = int(gpus_raw) if gpus_raw is not None else 1
num_envs_per_rank = _resolve_per_rank_num_envs(runner_args, gpus=gpus)
if gpus > 1 and "TORCHELASTIC_RUN_ID" not in os.environ:
_relaunch_under_torchrun(gpus, agent_cfg, runner_args, num_envs_per_rank, task_id=task_id)
return
if gpus > 1:
if backend.name != "rsl_rl":
raise SystemExit(
f"multi-GPU training (--gpus {gpus}) is only supported by the RSL-RL "
f"backend; this task uses the {backend.name!r} backend"
)
if "TORCHELASTIC_RUN_ID" not in os.environ:
_relaunch_under_torchrun(
gpus, agent_cfg, runner_args, num_envs_per_rank, task_id=task_id
)
return

max_iter_raw = runner_args.get("max_iterations")
seed_raw = runner_args.get("seed")
Expand Down
9 changes: 8 additions & 1 deletion src/genelab/rl/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""RL runner and VecEnv integration."""
"""RL runner, backend abstraction, and VecEnv integration."""

from genelab.rl.backends import Backend, select_backend
from genelab.rl.config import (
RslRlBaseRunnerCfg,
RslRlModelCfg,
Expand All @@ -9,18 +10,24 @@
from genelab.rl.profiler import maybe_profile, profiler_enabled
from genelab.rl.rsl_rl_wrapper import RslRlVecEnvWrapper
from genelab.rl.runner import AgentKind, play_task, train_task
from genelab.rl.skrl_config import SkrlAgentCfg, SkrlExperimentCfg, SkrlModelCfg
from genelab.rl.vecenv import VecEnvBase

__all__ = [
"AgentKind",
"Backend",
"RslRlBaseRunnerCfg",
"RslRlModelCfg",
"RslRlOnPolicyRunnerCfg",
"RslRlPpoAlgorithmCfg",
"RslRlVecEnvWrapper",
"SkrlAgentCfg",
"SkrlExperimentCfg",
"SkrlModelCfg",
"VecEnvBase",
"maybe_profile",
"play_task",
"profiler_enabled",
"select_backend",
"train_task",
]
Loading