Add TorchRL environment wrapper for isaaclab_rl - #7502
Conversation
Greptile SummaryThis PR adds an optional TorchRL dependency and a TorchRL EnvBase adapter for batched Isaac Lab environments, including structured specifications, terminal-observation handling, documentation, and tests.
Confidence Score: 4/5The PR should not merge until the constructor stops accepting non-RL ManagerBasedEnv variants or fully supports their different interface. The new type gate promises support for two environment classes that lack the spaces and five-value step result the wrapper unconditionally requires, causing construction or stepping to fail for those accepted inputs. Files Needing Attention: source/isaaclab_rl/isaaclab_rl/torchrl/vecenv_wrapper.py Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
T[TorchRL TensorDict action] --> W[IsaacLabTorchRLWrapper]
W --> E[Isaac Lab batched RL environment]
E --> S[Observation, reward, termination, truncation, extras]
S --> F{Final observation available?}
F -->|Yes| O[Use terminal observation on done rows]
F -->|No| N[Invalidate done-row observations]
O --> R[TorchRL next TensorDict]
N --> R
R --> A[Masked autoreset request]
A --> C[Return current post-reset Isaac Lab observations]
Reviews (1): Last reviewed commit: "Add Kit-dependent integration test for t..." | Re-trigger Greptile |
There was a problem hiding this comment.
Isaac Lab Review Bot
Adds a direct TorchRL EnvBase wrapper with specs, auto-reset handling, optional dependency metadata, documentation, and tests. Two contract mismatches need correction: the constructor accepts non-RL manager environments that _step cannot consume, and actions are not transferred back to the simulation device when the wrapper uses a different TensorDict device.
- Design and architecture: The direct
EnvBaseintegration preserves batched observation groups and explicitly handles Isaac Lab’s same-step auto-reset behavior. However, the accepted-type set includesManagerBasedEnvand its Warp counterpart even though the implementation requires the five-value RLstep()contract; construction should be restricted to supported RL environment classes. - API: The public wrapper is exported, documented, and backed by a dedicated
torchrlextra. The constructor’s runtime acceptance currently conflicts with its annotation and error message by permitting non-RL manager environments, while the documenteddeviceoption implies cross-device TensorDict support that the action path does not fully implement. - Implementation: Spec conversion, buffer cloning, finite-horizon handling, and terminal-observation behavior are covered by focused tests. Before stepping, actions must be moved from the wrapper device to
self.unwrapped.device; otherwise a non-default wrapper device produces device-mismatched simulator inputs.
Minor fixes needed. Posted 2 actionable findings inline.
Automated review; human maintainers own approval decisions.
| if self._clip_actions is not None: | ||
| actions = torch.clamp(actions, -self._clip_actions, self._clip_actions) | ||
|
|
||
| obs_dict, rew, terminated, truncated, extras = self.env.step(actions) |
There was a problem hiding this comment.
🟡 Warning · Implementation — Actions not moved to simulation device
The docstring advertises device as the device the tensordicts live on, and outputs are converted via the TensorDict(device=...) construction. Actions, however, are forwarded to self.env.step unchanged, so a wrapper constructed with a device different from self.unwrapped.device (e.g. device="cpu" over a CUDA env) hands device-mismatched tensors to the simulator. Convert actions to the environment device before stepping.
There was a problem hiding this comment.
The supported env classes already move the action to their own device at the top of step(): DirectRLEnv.step does action = action.to(self.device), ManagerBasedRLEnv.step calls process_action(action.to(self.device)), and DirectRLEnvWarp / ManagerBasedRLEnvWarp do the same before the Warp copy. A wrapper constructed with device="cpu" over a CUDA env therefore steps correctly as is, so I left this unchanged.
6fffac0 to
ebd47fc
Compare
IsaacLabTorchRLWrapper implements torchrl.envs.EnvBase directly on the batched Isaac Lab environment instead of going through GymWrapper, so the Warp-based environments work too and tensordicts stay on the simulation device. Observation groups map to one Composite spec each, done/terminated/truncated are separate keys, and the "next" observation of done rows carries extras["final_obs"] when the environment captures it. Reset requests that TorchRL issues after a step with done environments are served from the current observations, since Isaac Lab already reset those environments inside step(). Adds a torchrl extra, the API docs entry, and the changelog fragments.
Exercise spec conversion and the step/reset contract against a fake environment that mimics Isaac Lab's same-step auto-reset and in-place buffers, without launching Isaac Sim.
Mirror test_rsl_rl_wrapper.py: wrap the first five registered tasks and run check_env_specs and random-action rollouts through them.
ebd47fc to
703180a
Compare
| or :class:`DirectRLEnv`. | ||
| """ | ||
| # NOTE: import here (not at module level) to avoid loading heavy env classes before Isaac Sim is initialized. | ||
| from isaaclab.envs import DirectRLEnv, ManagerBasedRLEnv |
There was a problem hiding this comment.
I left it here just for performance reasons; however, open to migrating this to be global
|
thanks for the PR! do you happen to have an example cfg with torch RL that can be used for testing? maybe a cartpole training configuration? |
|
@kellyguo11 yeah! I'm running some cartpole ppo experiments. I also wanted to ask if it makes sense to keep this as a standalone env or subclass of torchRL's IsaacLabWrapper. |
|
@kellyguo11 this is the PR for the IsaacLab Wrapper pytorch/rl#2937 |
|
I think a standalone env class would make sense so that we can align it with the Isaac Lab design more closely and will be easier for us to maintain. would be good to have the cartpole example along with the PR so that we can review the full training setup. |
train_ppo builds a Gaussian MLP actor, an MLP critic (on the "critic" observation group when the task has a flat one), TorchRL's Collector, GAE and ClipPPOLoss, and runs the collect/update loop with TensorBoard logging of the losses and Isaac Lab's episode statistics. Actor checkpoints reload into make_actor. TorchRlPpoCfg is a single flat config so task configs and Hydra overrides stay short. Wire a torchrl backend into the unified train entrypoint (--rl_library torchrl) and register torchrl_cfg_entry_point PPO configs on Isaac-Cartpole and Isaac-Cartpole-Direct.
3b89ab9 to
53ba73a
Compare
|
@kellyguo11 ran a ppo experiment with cartpole and torchrl learns faster and its final policy balances the pole more reliably. The reward curve is more noisy later in training though. |
|
The only difference in the experiment setups which is what likely caused the noise was that RSL-RL uses adaptive kl while torchRL here just uses ppo loss. I can change that parameter setup, but the curves demonstrate that the wrapper works soundly with isaac lab |
|
Thanks, we'll let the team do a more thorough review of the changes. |
AntoineRichard
left a comment
There was a problem hiding this comment.
AI-generated review (Codex)
This review was produced by an AI coding agent and should be validated by a human maintainer.
I would request changes before merging.
Findings
-
Important —
pyproject.toml:156,test_torchrl_wrapper_specs.py:20:torchrlwas added topyproject.tomlbut notuv.lockor the CI environment.uv export --frozen --extra torchrlreports that the extra is undefined in the locked metadata, and both new test suites useimportorskip, so standard CI skips all TorchRL coverage. Regenerateuv.lockand install the extra in theisaaclab_rltest lane. -
Important —
vecenv_wrapper.py:196,train_torchrl.py:106: convertedDirectMARLEnvtasks never receive terminal observations. The MARL environment stores them underextras[agent]["final_obs"], whilemulti_agent_to_single_agentpasses that structure unchanged and the wrapper only checks top-levelextras["final_obs"]. Timeouts therefore produce NaN next-observations instead of values suitable for PPO bootstrapping. Convert per-agent terminal observations alongside normal observations and add a MARL-timeout regression test. -
Important —
vecenv_wrapper.py:141: the publicdeviceoption converts outputs but not inputs. With a CUDA simulation wrapped usingdevice="cpu", TorchRL produces CPU actions and_stepforwards them directly into CUDA task code. Move actions toself.unwrapped.devicebefore stepping and test cross-device operation. -
Important —
ppo.py:29: the exported PPO API accepts anyIsaacLabTorchRLWrapper, but assumes flat continuous actions and flat"policy"observations. A(2, 3)Box produces(N, 3)actions rather than(N, 2, 3); Discrete/MultiDiscrete spaces receive Gaussian floats; Composite policy observations reach an MLP expecting a tensor. Either implement flattening/reshaping and categorical policies or reject unsupported specs explicitly. -
Important —
ppo.py:45: when a"critic"group is Composite,make_criticsilently switches to"policy", discarding privileged observations. This contradictsppo_cfg.py:33. Flatten/encode Composite critic observations or raise an unsupported-space error. -
Important —
vecenv_wrapper.py:196:extras["final_obs"]persists between steps, but the wrapper treats key presence as validity for every current done row. Visualizer-triggered resets occur after normal terminal-observation capture, so they can receive a stale terminal observation from an earlier episode. Clear/carry a validity mask per step and capture manual-reset observations before reset. -
Important —
tools/environ_docs.py:52: the generated environment browser was not refreshed.tools/update_environments_rst.py --checkfails, and regeneration adds TorchRL to both Cartpole rows. The browser also lacks atorchrlextra mapping and enables playback even thoughdispatch.py:30has no TorchRL play backend. Commit the generated rows, add the extra mapping, and either implement playback or make the UI/docs explicitly train-only. -
Important —
train_torchrl.py:116,ppo.py:103:KeyboardInterruptunwindstrain_ppobefore the collector and TensorBoard writer are closed, then is suppressed and reported like successful completion. Other exceptions additionally bypass environment cleanup. Put collector, writer, and environment cleanup infinallyblocks and report interruptions distinctly.
Validation
git diff --check: passed.- Changelog validation: passed.
- Root pyproject tests: 11 passed.
- Environment documentation check: failed as described.
- TorchRL unit suite: skipped because TorchRL is absent.
- Full simulator tests were not rerun.

Description
Adds
isaaclab_rl.torchrl.IsaacLabTorchRLWrapper, atorchrl.envs.EnvBaseimplementation over Isaac Lab's batched environments, together with atorchrlextra, API docs, and tests.Validated on an A100-40GB (Isaac Sim 6.0, torchrl 0.13.3, 64 envs per task):
test_torchrl_wrapper_specs.py(kit-free)test_torchrl_wrapper.py(first 5 registered tasks)torchrl.envs.libs.isaac_lab.IsaacLabWrappernative_autoreset=True) / 10.4k (False) with NaN next-observations on done rows