Skip to content

Commit 80d5db6

Browse files
committed
Project import generated by Copybara.
GitOrigin-RevId: 10125061d911c5f218500143a14b10f237c57b70
1 parent 79d8aed commit 80d5db6

1 file changed

Lines changed: 219 additions & 0 deletions

File tree

misc/isaac_lab_rl.py

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Train a robot in a simulated environment with Isaac Lab and Modal.
2+
#
3+
# In this example, we'll use Modal to train a robot model in a simluated environment using an L40S GPU.
4+
# Specifically, we'll run a headless instance of Isaac Lab to train a policy that teaches Anymal-C,
5+
# a quadruped robot, to obey a velocity command and walk over rough terrain.
6+
#
7+
# Isaac Lab is NVIDIA's open source python framework for robot learning with GPUs. It's built on top of Isaac Sim,
8+
# NVIDIA's open source robotics simulation platform. Isaac Sim utilizes Omniverse (simulation and rendering)
9+
# and PhysX (physics engine), which both take advantage of GPUs for acceleration.
10+
#
11+
# Isaac Lab integrates with a variety of RL frameworks. Today, we'll use rl-games, an open source
12+
# reinforcement learning library for robotics training, with PPO as the training algorithm. All of
13+
# these details are transparent to our use, as Isaac Lab ships a pre-made `task` for training a quadruped
14+
# to follow a velocity command.
15+
# see: https://github.com/isaac-sim/IsaacLab/blob/main/source/isaaclab_tasks/isaaclab_tasks/manager_based/locomotion/velocity
16+
#
17+
# modal run isaac_lab_rl.py
18+
# Output mp4 is written to the `isaac-demo-output` Volume (and is downloadable).
19+
20+
import shutil
21+
import subprocess
22+
from pathlib import Path
23+
24+
import modal
25+
26+
# NVIDIA's official container bundles Isaac Lab and all the necessary dependencies for this example.
27+
image = modal.Image.from_registry(
28+
"nvcr.io/nvidia/isaac-lab:3.0.0-beta2-post1", add_python="3.11"
29+
)
30+
# IMPORTANT: the isaac-lab image's ENTRYPOINT runs `/isaac-sim/runheadless.sh`, and never
31+
# execs the arguments passed to it, which is a requirement for Modal Images, so we must override it.
32+
# see https://modal.com/docs/guide/existing-images#entrypoint
33+
image = (
34+
image.entrypoint([])
35+
.env({"ACCEPT_EULA": "Y", "HYDRA_FULL_ERROR": "1"})
36+
.run_commands("/workspace/isaaclab/isaaclab.sh -i rl_games")
37+
)
38+
39+
app = modal.App("example-isaac-lab-rl")
40+
41+
# Persisted shader cache at /root/.cache/ov, should reduce cold start times after first run.
42+
ov_cache = modal.Volume.from_name("isaac-ov-cache", create_if_missing=True)
43+
# Outputs (rendered mp4s) so you can grab them after the run.
44+
output_vol = modal.Volume.from_name("isaac-demo-output", create_if_missing=True)
45+
46+
OUTPUT_PATH = "/output"
47+
48+
49+
# Training here is PPO via rl-games: thousands of robots run in parallel at each
50+
# iteration, each gets a random commanded velocity,
51+
# and a reward (track velocity, keep back horizontal, etc) shapes the
52+
# policy. Rough terrain adds a curriculum that ramps up difficulty.
53+
# We train once, then render the final checkpoint and record it as a video.
54+
@app.function(
55+
image=image,
56+
gpu="L40S:1",
57+
volumes={
58+
"/root/.cache/ov": ov_cache,
59+
OUTPUT_PATH: output_vol,
60+
},
61+
timeout=60 * 60,
62+
)
63+
def train_and_render_demo(
64+
train_task: str = "Isaac-Velocity-Rough-Anymal-C-v0",
65+
play_task: str = "Isaac-Velocity-Rough-Anymal-C-Play-v0",
66+
video_length: int = 200,
67+
num_envs: int = 4096,
68+
iterations: int = 125,
69+
play_seed: int = 3,
70+
):
71+
import time
72+
73+
start_time = time.time()
74+
75+
# First we train the policy using the rl-games training script baked into the image, implemented here:
76+
# https://github.com/isaac-sim/IsaacLab/blob/b0542fe2d45bf91c4e1d9ef6952b9c709c80b4e8/scripts/reinforcement_learning/rl_games/train.py
77+
# this is a thin script that is mainly responsible for instantiating a Gymnasium environment based
78+
# on the provided `task` and mediating the data exchange between rl-games' training runner and the environment.
79+
# `--viz none` disables all visualizers, so the simulation runs headless.
80+
run_name = "training_run"
81+
subprocess.run(
82+
[
83+
"/workspace/isaaclab/isaaclab.sh",
84+
"-p",
85+
"scripts/reinforcement_learning/train.py",
86+
"--rl_library",
87+
"rl_games",
88+
"--task",
89+
train_task,
90+
"--viz",
91+
"none",
92+
"--num_envs",
93+
str(num_envs),
94+
"--max_iterations",
95+
str(iterations),
96+
"--kit_args",
97+
"--/log/level=error --/log/fileLogLevel=error --/log/outputStreamLevel=error --/omni.kit.plugin/usdMuteDiagnosticMessage=true",
98+
f"agent.params.config.full_experiment_name={run_name}",
99+
"agent.params.config.save_frequency=25",
100+
],
101+
check=True,
102+
cwd="/workspace/isaaclab",
103+
)
104+
print("Training completed")
105+
106+
# Once we have trained the model, we grab the latest checkpoint and play a demo simulation at it.
107+
# We'll copy the video to the Volume-mounted output path so that the results are persisted
108+
# and can be downloaded later.
109+
110+
checkpoint = _latest_checkpoint(run_name)
111+
112+
clip = _render(
113+
checkpoint=checkpoint,
114+
play_task=play_task,
115+
video_length=video_length,
116+
play_seed=play_seed,
117+
)
118+
print(f"Download with: `modal volume get isaac-demo-output {clip}`")
119+
120+
end_time = time.time()
121+
print(f"Time taken: {end_time - start_time} seconds")
122+
123+
124+
@app.local_entrypoint()
125+
def main(
126+
train_task: str = "Isaac-Velocity-Rough-Anymal-C-v0",
127+
play_task: str = "Isaac-Velocity-Rough-Anymal-C-Play-v0",
128+
video_length: int = 200,
129+
num_envs: int = 4096,
130+
iterations: int = 80,
131+
play_seed: int = 3,
132+
):
133+
train_and_render_demo.remote(
134+
train_task=train_task,
135+
play_task=play_task,
136+
video_length=video_length,
137+
num_envs=num_envs,
138+
iterations=iterations,
139+
play_seed=play_seed,
140+
)
141+
142+
143+
# Demo rendering utility
144+
def _render(
145+
checkpoint,
146+
play_task: str,
147+
video_length: int,
148+
play_seed: int,
149+
):
150+
x_vel, y_vel, yaw_vel = 1.0, 0.0, 0.0
151+
cmd = [
152+
"/workspace/isaaclab/isaaclab.sh",
153+
"-p",
154+
"scripts/reinforcement_learning/play.py",
155+
"--rl_library",
156+
"rl_games",
157+
"--task",
158+
play_task,
159+
"--viz",
160+
"none",
161+
"--enable_cameras",
162+
"--device",
163+
"cuda:0",
164+
"--num_envs",
165+
"1",
166+
"--video",
167+
"--video_length",
168+
str(video_length),
169+
"--seed",
170+
str(play_seed),
171+
"--kit_args",
172+
"--/log/level=error --/log/fileLogLevel=error --/log/outputStreamLevel=error --/omni.kit.plugin/usdMuteDiagnosticMessage=true",
173+
"--checkpoint",
174+
checkpoint,
175+
"env.viewer.origin_type=world",
176+
"env.viewer.eye=[5.2,5.2,2.7]",
177+
"env.viewer.lookat=[0.0,0.0,0.55]",
178+
"env.scene.terrain.terrain_generator.num_rows=1",
179+
"env.scene.terrain.terrain_generator.num_cols=1",
180+
"env.scene.terrain.max_init_terrain_level=0",
181+
"env.scene.terrain.terrain_generator.sub_terrains.pyramid_stairs.proportion=1.0",
182+
"env.scene.terrain.terrain_generator.sub_terrains.pyramid_stairs_inv.proportion=0.0",
183+
"env.scene.terrain.terrain_generator.sub_terrains.boxes.proportion=0.0",
184+
"env.scene.terrain.terrain_generator.sub_terrains.random_rough.proportion=0.0",
185+
"env.scene.terrain.terrain_generator.sub_terrains.hf_pyramid_slope.proportion=0.0",
186+
"env.scene.terrain.terrain_generator.sub_terrains.hf_pyramid_slope_inv.proportion=0.0",
187+
"env.commands.base_velocity.debug_vis=false",
188+
f"env.commands.base_velocity.ranges.lin_vel_x=[{x_vel},{x_vel}]",
189+
f"env.commands.base_velocity.ranges.lin_vel_y=[{y_vel},{y_vel}]",
190+
f"env.commands.base_velocity.ranges.ang_vel_z=[{yaw_vel},{yaw_vel}]",
191+
"env.commands.base_velocity.heading_command=false",
192+
"env.commands.base_velocity.ranges.heading=[0.0,0.0]",
193+
]
194+
subprocess.run(cmd, check=True, cwd="/workspace/isaaclab")
195+
196+
recording_dir = Path(checkpoint).parent.parent / "videos" / "play"
197+
recordings = sorted(recording_dir.glob("*.mp4"))
198+
if not recordings:
199+
raise FileNotFoundError(f"No recorded video in {recording_dir}.")
200+
201+
# Copy the video to the Volume-mounted output path
202+
video_name = f"{play_task}.mp4"
203+
shutil.copyfile(recordings[0], Path(OUTPUT_PATH) / video_name)
204+
return video_name
205+
206+
207+
def _latest_checkpoint(run_name: str) -> str:
208+
import glob
209+
import re
210+
211+
ckpts = glob.glob(f"/workspace/isaaclab/logs/rl_games/*/*{run_name}/nn/*.pth")
212+
if not ckpts:
213+
raise FileNotFoundError(f"No .pth checkpoints for run matching '{run_name}'.")
214+
215+
def iteration(path: str) -> int:
216+
match = re.search(r"_ep_(\d+)", Path(path).name)
217+
return int(match.group(1)) if match else 0
218+
219+
return max(ckpts, key=iteration)

0 commit comments

Comments
 (0)