NVIDIA Warp と MuJoCo Warp (MJWarp) でロボット用 2,048 ワールド GPU シミュレーションを実現
TL;DR
NVIDIA Warp とその MuJoCo 互換レイヤーである MJWarp を使用すると、1 つの GPU 上で数千ものロボットシミュレーションワールドをバッチ処理でき、従来の CPU 基準 MuJoCo と比べて桁違いに高い集約スループットを達成できます。
スタック概要
| レイヤー | 役割 |
|---|---|
| NVIDIA Warp | CUDA にコンパイルされる Python カーネル言語。自動微分を提供し、PyTorch/JAX と相互運用可能 |
| MJWarp | MuJoCo 物理の GPU 実装。MJCF 形式を保持し、バッチステップをサポート |
| あなたのシーン (SO‑101) | 標準的な Menagerie / Robot Studio アセットとタスクジオメトリ |
| Next (Newton / Isaac Lab) | マルチソルバー API、USD アセット、センサー、マネージャー、トレーニングループ |
各コンポーネントの使用タイミング
| 必要な機能 | 使用するもの |
|---|---|
| 単一ロボットの MPC や遠隔操作 | 従来の MuJoCo CPU |
| 最大の MuJoCo 物理スループット | MJWarp (または mjlab) |
| JAX をベースにしたトレーニングレシピ | MuJoCo Playground / MJX (実装 = warp) |
| マルチソルバー + Isaac Lab 統合 | Newton (今後公開予定) |
NVIDIA Warp: 最小限の GPU カーネル
Warp を使用すると、静的に型指定された Python カーネルを JIT コンパイルしてネイティブ CUDA に変換できます。以下の例では、重力下での点の位置を統合しており、2 点から数百万点まで、カーネルロジックを変更せずにスケーリング可能です。
import numpy as np, warp as wp
@wp.kernel
def integrate(positions: wp.array[wp.vec3],
velocities: wp.array[wp.vec3],
dt: float):
i = wp.tid()
velocities[i] += wp.vec3(0.0, 0.0, -9.81) * dt
positions[i] += velocities[i] * dt
wp.init()
device = "cuda:0" if wp.is_cuda_available() else "cpu"
start = np.array([[0.0, 0.0, 0.5], [0.2, 0.0, 0.5]], dtype=np.float32)
positions = wp.array(start, dtype=wp.vec3, device=device)
velocities = wp.zeros_like(positions)
wp.launch(integrate, dim=len(start), inputs=[positions, velocities, 0.01], device=device)
wp.synchronize_device(device)
print(positions.numpy())
ロボット工学における重要性
- 明示的な並列処理 –
wp.tid()は論理スレッド(点、接触、剛体、またはワールド)を識別します。 - 明示的なデバイス配列 – 配列は選択されたデバイス上に存在します。
.numpy()はホストへのコピーをトリガーし、DLPack アダプタにより PyTorch/JAX とのゼロコピー共有が可能になります。 - 合成可能な起動 – 複数のカーネルを CUDA グラフにキャプチャでき、起動オーバーヘッドを削減できます。
微分可能性と決定性(Warp の機能)
- 微分可能なカーネル –
wp.Tapeは前方起動を記録し、backward()で自動的に随伴を計算します。これにより、微分可能なジオメトリ、CFD、カスタム物理パイプラインが可能になります。 - 決定的実行 – Warp 1.15 で導入。決定的モードはわずかなパフォーマンスの低下を犠牲に、再現可能なアトミック順序を提供し、レグレッションテストに有用です。これらの機能はオプションであり、SO‑101 のウォークスルーには必須ではありません。
MuJoCo Warp (MJWarp) とは?
MJWarp は Warp 上に構築された MuJoCo 物理パイプラインの GPU ベース実装です。同じ MJCF モデルを受け入れ、モデルと複数の独立した状態を GPU に配置し、単一の mjw.step 呼び出しで全バッチを進めることが可能です。
- レイテンシ vs スループット – MJWarp は単一ワールドステップの高速化を保証しません。その利点は 集約スループット にあります。すなわち、多数のワールドを並列で実行した場合の 1 秒あたりのワールドステップ総数です。これは強化学習や大規模サンプリングの主要な指標です。
MuJoCo シーンを MJWarp に移行する方法
ガイドでは、3 つの段階を経て説明します。CPU ベースラインの確立、1 ワールド GPU ロールアウトの検証、そして 2,048 ワールドへのスケーリングです。
1. MuJoCo CPU ベースラインの確立
mujoco.MjModel.from_xml_pathを使用して、SO‑101 ピックアンドプレース MJCF(標準的な Menagerie アセット)を読み込みます。- 50 Hz の制御ループと 10 回の物理サブステップ(
timestep = 0.002 s)を使用します。 - キューブが安定した後、水平誤差 ≤ 0.015 m かつ垂直方向の分離が 0.035 m から 0.055 m の範囲内であることを確認して、タスクの成功を検証します。
2. 1 ワールド MJWarp の同等性検証
import warp as wp, mujoco_warp as mjw
wp.init()
device = wp.get_device()
# モデルのアップロードと単一ワールドバッチの割り当て
m = mjw.put_model(mjm)
d = mjw.make_data(mjm, nworld=1, nconmax=spec.nconmax, njmax=spec.njmax)
# CPU ロールアウトからの状態を初期化
wp.copy(d.qpos, wp.array(mjd.qpos[None, :], dtype=wp.float32, device=device))
wp.copy(d.qvel, wp.array(mjd.qvel[None, :], dtype=wp.float32, device=device))
wp.copy(d.ctrl, wp.array(mjd.ctrl[None, :], dtype=wp.float32, device=device))
mjw.forward(m, d)
- ホストループは CPU ワークフローを模倣しており、各サブステップでコントロールをデバイスにコピーし、
qpos/qvelを後続計算のために取得します。 - このパスはパフォーマンスベンチマークではありません。GPU シミュレーションが CPU 結果を再現していることを検証するためのものです。
3. 接触および制約バッファのサイズ調整
- MJWarp は各ワールドに対して接触(
nconmax)および制約(njmax)バッファを事前に割り当てます。容量が不足するとオーバーフロー警告が発生し、トラジェクトリが破損する可能性があります。 mjwarp-testspeed --measure_allocを使用して実際の使用量を測定し、必要に応じて制限を引き上げます(例:SO‑101 タスクではnconmax=128、njmax=300)。
4. 2,048 ワールドへのスケーリング
nworld = 2_048
d = mjw.make_data(m, nworld=nworld, nconmax=spec.nconmax, njmax=spec.njmax)
# 初期状態をバッチ全体に複製
wp.copy(d.qpos, wp.array(np.tile(mjd.qpos, (nworld, 1)), dtype=wp.float32, device=device))
wp.copy(d.qvel, wp.array(np.tile(mjd.qvel, (nworld, 1)), dtype=wp.float32, device=device))
wp.copy(d.ctrl, wp.array(np.tile(mjd.ctrl, (nworld, 1)), dtype=wp.float32, device=device))
mjw.forward(m, d)
# ステップを CUDA グラフにキャプチャして低オーバーヘッドの再実行を可能に
with wp.ScopedCapture() as cap:
mjw.step(m, d)
step_graph = cap.graph
- キャプチャされたグラフは同じバッファを再利用します。再実行間で更新が必要なのは
d.ctrlのみです。
5. 集約スループットの測定
# ワームアップ(コンパイル、割り当て)
for _ in range(10):
wp.capture_launch(step_graph)
wp.synchronize()
t0 = time.perf_counter()
for _ in range(200):
wp.capture_launch(step_graph)
wp.synchronize()
elapsed = time.perf_counter() - t0
world_steps = 200 * nworld
print(f"{world_steps/elapsed:,.0f} world‑steps/second")
- バッチサイズとともに、ワールドステップ/秒 と バッチステップあたりのミリ秒 の両方を報告してください。
- 提供された
scaling_study.pyスクリプトを使用して、バッチサイズ(1、64、1,024、2,048、8,192)をスイープし、スループットが飽和するポイントを観察してください。
はじめ方
- Warp –
pip install warp-lang(決定的モードには ≥ 1.15 必須)。python -m warp.examples.browseで例を探索。 - MJWarp –
pip install mujoco-warp;mjwarp-viewer path/to/scene.xmlでビューアを起動。 - SO‑101 リソース – NVIDIA の Sim‑to‑Real コースと Menagerie アセット。
- MJWarp 上でのトレーニング –
mjlab、MuJoCo Playground(MJXimpl='warp')、または近日公開予定の Newton 統合による Isaac Lab。
次回予告
次回の投稿では、MJWarp を Newton 物理エンジン内に統合し、マルチフォーマットアセット、交換可能なソルバー、センサー、および Isaac Lab トレーニングパイプラインを追加します。同じ SO‑101 タスク(およびオプションの reBot バリアント)を再利用して、追加の統合ステップを説明します。
参考文献
- Physical AI におけるシミュレーションの現状 – このシリーズの最初のブログ。
- NVIDIA Warp GitHub とドキュメント(v1.15.0 で GPU の決定性が追加)。
- MuJoCo Warp リポジトリと公式ドキュメント。
- mjlab(arXiv:2601.22074)と MuJoCo Playground。
- NVIDIA SO‑101 シミュレーションから実世界への学習パス。
- 今後公開予定の Newton リポジトリ。