Using Eden with Another RL Library¶
eden train drives rsl_rl and rl_games through a task's runner_options (Training Runs). Any
other RL library reaches an Eden task through a wrapper around the RLEnvBase. This page covers the gymnasium vector
env (for CleanRL or a loop of your own), stable-baselines3 and skrl, and how to add a wrapper for another library.
| Library | Entry point | Install |
|---|---|---|
| CleanRL, or any loop of your own | gym.make_vec("Eden/<task>", num_envs=N) or GymnasiumVecEnvWrapper (a gymnasium VectorEnv of torch tensors) |
core |
| stable-baselines3 | Sb3VecEnvWrapper (numpy, one observation group) |
pip install 'eden-world[sb3]' |
| skrl | SkrlVecEnvWrapper (torch, optional critic state) |
pip install 'eden-world[skrl]' |
In a checkout, install the extras with uv sync --inexact --extra sb3 or uv sync --inexact --extra skrl.
Runnable examples train the reacher task with each library:
examples/recipes/rl_libraries/cleanrl_ppo.py, sb3_ppo.py and skrl_ppo.py.
The gymnasium vector env¶
import eden as en
import genesis as gs
import gymnasium as gym
from eden.envs.wrappers.gymnasium_env import register_gymnasium_envs
en.init(backend=gs.gpu)
register_gymnasium_envs() # every registered task, as "Eden/<task>"
envs = gym.make_vec("Eden/reacher", num_envs=4096, seed=0)
obs, info = envs.reset(seed=0)
obs, reward, terminated, truncated, info = envs.step(actions) # actions: (4096, action_dim)
Registration¶
register_gymnasium_envs() registers every task in the task registry as Eden/<task> and returns the ids it added.
It builds nothing and imports no task module or Genesis, so it is cheap, but import eden does not run it: call it
once. Calling it again registers only tasks added since (a task registered at runtime, or one from an installed
package).
make_vec, not make¶
Every Eden env is batched, so gym.make("Eden/<task>") raises and names make_vec, as does gym.make_vec with
vectorization_mode="sync" or "async". Do not wrap a num_envs=1 env to fake a single one: its tensors keep the
leading env dimension. make_vec needs eden.init() first and takes:
num_envs: replaces the task'senv_options.num_envs. Gymnasium passes its own default of 1 if you leave it out, never the task's.seed: seeds every RNG before the build (Seeding), so the build, the first reset and domain randomization are reproducible. Without it the process RNG is left alone.clip_actions: clamps actions to[-clip_actions, clip_actions]and bounds the action space by it. Without it actions reach the action manager unclamped and the space is unbounded.final_observations(default on): reportfinal_obsininfo(below). Turning it off saves an observation compute per step.random_episode_starts(default off): stagger the episode clocks on everyreset(below).show_viewer,eval_mode: as for any env'sfrom_config.- Any other keyword: the task's TaskMod parameters, as
TASK_REGISTRY.build(task, **kwargs)takes them, for examplegym.make_vec("Eden/reacher", num_envs=64, difficulty="hard").
From a config you already hold, GymnasiumVecEnvWrapper.from_config(cfg, clip_actions=..., seed=...) does the same,
as do the SB3 and skrl wrappers' from_config. cfg needs no runner_options and is left untouched. Each wrapper
also wraps an env you built yourself: GymnasiumVecEnvWrapper(env).
Tensors stay on the device¶
Observations, rewards, terminated and truncated are torch tensors on the sim's device (envs.device); actions
may be a tensor or a numpy array. Gymnasium's own vector wrappers (RecordEpisodeStatistics, NormalizeObservation,
…) are numpy-based and do not apply; the wrapper reports episode statistics itself (below).
Everything step and reset return is a copy, so a learner may keep a step's tensors past the next step (the env
itself reuses its buffers in place). That costs one copy of the observation per step, two with final_obs. The SB3
and skrl wrappers copy, and return, only the groups they read.
Observations are the env's observation groups¶
obs is a dict with one entry per observation group, and single_observation_space is a gymnasium.spaces.Dict of
them: the actor reads obs["policy"], an asymmetric critic another group. A group that concatenates its terms is a
Box; one with concatenate_terms=False is a Dict of its terms. The spaces are read off a real observation when the
wrapper is constructed (which resets every env), so shapes and dtypes are the manager's.
Done envs reset in the same step¶
metadata["autoreset_mode"] is gymnasium's SAME_STEP: as in RLEnvBase.step(), the observation step returns for
a done env already starts its next episode. info carries what the episode ended with:
| Key | Value |
|---|---|
final_obs / _final_obs (with final_observations) |
The observation each done env ended its episode on, and the mask of envs it is valid for: done, and not a state Genesis flagged as corrupt (a solver error, which Eden reports as truncated). Rows outside the mask are zero. |
episode / _episode |
{"r": return, "l": length} of each episode that ended this step (zero elsewhere), and the done mask. |
log |
The managers' episode statistics, as the rsl_rl and rl_games wrappers log them. |
terminated and truncated split the done envs by the termination terms' time_out flag. An env can be both on one
step (a failure on the step the episode timed out); treat it as terminal. To bootstrap a time-out correctly, value
info["final_obs"], not the returned observation, for the envs in truncated & ~terminated & info["_final_obs"].
Select with torch.where rather than multiplying by the mask, so no other row's value can leak in;
cleanrl_ppo.py shows the two lines it takes.
final_obs needs env_options.record_final_observations, which costs one extra observation compute per step (for a
camera task, a second render). from_config and make_vec set it to match final_observations. Wrapping an env built
without it raises and says so, unless you pass final_observations=False. Turn it off for a loop that never reads
final_obs: an evaluation rollout, or skrl, whose wrapper leaves it off by default. The SB3 wrapper always needs it.
Staggered episode starts¶
Every env resets together and the episode length is fixed, so without help the envs that never terminate early all
time out on the same step, and a learner's per-episode work (SB3 values each ended episode separately) arrives in
bursts. random_episode_starts=True starts each env's episode clock at a random step in [0, max_episode_length) on
every reset, as eden train does (rsl_rl's init_at_random_ep_len). The first episodes are then shorter, and end
as real time-outs. The clocks are drawn after reset(seed=...) seeds the RNGs, so they are reproducible. The three
recipes turn it on.
Seeding and closing¶
reset(seed=s) seeds the env's RNGs before resetting every env. Eden's envs share one set of RNGs, so a list of
per-env seeds is refused, as are reset options (configure the task through its config or TaskMod keywords).
close() destroys the scene only when the wrapper built the env (from_config, make_vec); an env you passed to
GymnasiumVecEnvWrapper(env) stays yours to destroy. No wrapper destroys anything on garbage collection, because a
finalizer can run after Genesis has shut down.
stable-baselines3¶
from stable_baselines3 import PPO
from eden.envs.wrappers.sb3_env import Sb3VecEnvWrapper
from eden.tasks import TASK_REGISTRY
cfg = TASK_REGISTRY.build("reacher")
cfg.env_options.num_envs = 256
env = Sb3VecEnvWrapper.from_config(cfg, clip_actions=1.0)
model = PPO("MlpPolicy", env, n_steps=24, batch_size=256 * 6)
model.learn(total_timesteps=1_000_000)
(Call eden.init() first, as above.)
- The action space must be bounded. Every SB3 algorithm asserts finite action bounds, so
clip_actionsis required; use the task'srunner_options.clip_actionswhen it has one. SB3 clips each sampled action to that bound. - One observation group,
obs_group(default"policy"). ABoxgroup trains with"MlpPolicy"; a group withconcatenate_terms=Falseis aDictand needs"MultiInputPolicy". - Episode ends. Each done env's info carries
terminal_observation(SB3 bootstraps time-outs from it, and the off-policy algorithms store it as the transition's next observation),TimeLimit.truncated(truncated and not terminated) andepisode, which SB3 logs asrollout/ep_rew_mean; noVecMonitoris needed. A solver-error env gets noterminal_observation, so SB3 does not value its corrupt state. The wrapper therefore refuses an env that does not record final observations;from_configturns them on. - No reset options.
set_optionswith anything in it raises at the next reset. - Throughput. SB3 is numpy-based and single-device: each step copies observations, rewards and dones to the host,
and SB3 builds one info dict per env. Expect a fraction of
eden train's throughput, and use a few hundred envs rather than thousands.
get_attr returns the batched env's attribute once per env index; set_attr and env_method raise, since there are
no per-env objects to address.
skrl¶
from skrl.utils.runner.torch import Runner
from eden.envs.wrappers.skrl_env import SkrlVecEnvWrapper
from eden.tasks import TASK_REGISTRY
env = SkrlVecEnvWrapper.from_config(TASK_REGISTRY.build("reacher"), state_group="critic")
Runner(env, cfg).run("train") # cfg: skrl's YAML layout, as a dict
- The wrapper keeps everything on the device. The policy observes
obs_group(default"policy"); withstate_groupset, that group is skrl'sstate, what a value network withinput: STATESreads. Both are flattened to(num_envs, D). - Rewards,
terminatedandtruncatedare(num_envs, 1), andtruncatedleaves out an env that also terminated. infois the shared core's, with everylogvalue a 0-d tensor, so a trainerenvironment_info: "log"puts Eden's per-term episode statistics (Episode_Reward/<term>, …) in skrl's TensorBoard.- skrl values a time-out from the observation
stepreturns, which here (as in Isaac Lab) already starts the next episode, whethertime_limit_bootstrapis on or off. Its agents never readfinal_obs, so the skrl wrapper leaves final observations off (final_observations=Trueto have them). - skrl's
Wrapperanswers any attribute the wrapper lacks from the wrapped env, sowrapper.episode_length_bufworks here but raisesAttributeErroron the gymnasium and SB3 wrappers. Reach the env throughwrapper.envto keep code portable between them.
Normalize the inputs
On reacher, skrl's PPO needed RunningStandardScaler for the observation, state and value preprocessors;
without them it stayed at a do-nothing policy's reward. skrl_ppo.py carries a configuration that learns.
CleanRL¶
CleanRL is a set of single-file algorithms, not a library, so there is nothing to adapt: its continuous PPO runs on
gym.make_vec(...) as it is, once it indexes obs["policy"]. The tensors never leave the device, as in CleanRL's
ppo_continuous_action_isaacgym.py, which cleanrl_ppo.py follows.
What these entry points do not do¶
eden train,eden inference,eden exportandeden deploystay rsl_rl/rl_games-only. The libraries above take norunner_optionsand write their checkpoints in their own formats.- They run on one device: Eden's DDP setup is rsl_rl's.
- The wrappers do not split multi-agent tasks; rl_games'
agentsoption does.
Adding another library¶
All five wrappers (rsl_rl, rl_games, gymnasium, SB3, skrl) live in eden/envs/wrappers/ and share
core.py's EnvWrapperMixin: the build and env ownership, the action clamp and space, the observation spaces and the
split of terminated from timed-out envs. The gymnasium, SB3 and skrl wrappers add VecEnvMixin
(vec_env_mixin.py): final observations, episode statistics and the copies. To add a library:
- Mix
VecEnvMixinin beside the library's base class:class FooVecEnvWrapper(VecEnvMixin, FooVecEnv). - In
__init__, call_init_vec_env(env, ...), after the base class's own__init__if that routes attribute lookups to the env (as skrl's does). The mixin sets nonum_envsordevice: those names belong to the library's base class. - Build
reset,stepandcloseon what_reset_env,_step_envand_close_envreturn, converted to the library's types. A learner that bootstraps time-outs readscore.time_outs(terminated, truncated), which leaves out an env that also terminated. - Have
from_configcall_from_config. - Put the wrapper in
eden/envs/wrappers/<library>_env.py, import the library at module level, declare it as an extra inpyproject.toml, andpytest.importorskipit in the wrapper's test.
See also¶
- Training Runs:
eden trainand seeding - Environments: the step loop and auto-reset
- Observations: observation groups and
concatenate_terms