Add graph mode to MJX, in preparation of supporting staged graph capture.
PiperOrigin-RevId: 865380826 Change-Id: I840b48027e1142f87eb745d0d19b0cd3e87904b2
This commit is contained in:
committed by
Copybara-Service
parent
b37caf6d0c
commit
d07f39b4a0
+1
-1
@@ -84,7 +84,7 @@ General
|
|||||||
MJX
|
MJX
|
||||||
^^^
|
^^^
|
||||||
- Added ``actuator_length``, ``cdof`` and ``cdof_dof`` fields to ``mjx.Data``.
|
- Added ``actuator_length``, ``cdof`` and ``cdof_dof`` fields to ``mjx.Data``.
|
||||||
|
- Add ``graph_mode`` argument to ``put_model`` to support multiple Warp graph capture modes.
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|||||||
+18
-11
@@ -50,6 +50,11 @@ def _is_cuda_gpu_device(device: jax.Device) -> bool:
|
|||||||
return device in jax.devices('cuda')
|
return device in jax.devices('cuda')
|
||||||
|
|
||||||
|
|
||||||
|
def _check_warp_installed():
|
||||||
|
if not mjxw.WARP_INSTALLED:
|
||||||
|
raise RuntimeError('warp-lang is not installed. Cannot use WARP implementation of MJX.')
|
||||||
|
|
||||||
|
|
||||||
def _resolve_impl(
|
def _resolve_impl(
|
||||||
device: jax.Device,
|
device: jax.Device,
|
||||||
) -> types.Impl:
|
) -> types.Impl:
|
||||||
@@ -119,10 +124,7 @@ def _check_impl_device_compatibility(
|
|||||||
'Warp implementation requires a CUDA GPU device, got '
|
'Warp implementation requires a CUDA GPU device, got '
|
||||||
f'{device}.'
|
f'{device}.'
|
||||||
)
|
)
|
||||||
if not mjxw.WARP_INSTALLED:
|
_check_warp_installed()
|
||||||
raise RuntimeError(
|
|
||||||
'Warp is not installed. Cannot use Warp implementation of MJX.'
|
|
||||||
)
|
|
||||||
|
|
||||||
is_cpu_device = device.platform == 'cpu'
|
is_cpu_device = device.platform == 'cpu'
|
||||||
if impl == types.Impl.C or impl == types.Impl.CPP:
|
if impl == types.Impl.C or impl == types.Impl.CPP:
|
||||||
@@ -448,12 +450,10 @@ def _put_model_c(
|
|||||||
|
|
||||||
def _put_model_warp(
|
def _put_model_warp(
|
||||||
m: mujoco.MjModel,
|
m: mujoco.MjModel,
|
||||||
|
graph_mode: mjxw.types.GraphMode,
|
||||||
device: Optional[jax.Device] = None,
|
device: Optional[jax.Device] = None,
|
||||||
) -> types.Model:
|
) -> types.Model:
|
||||||
"""Puts mujoco.MjModel onto a device, resulting in mjx.Model."""
|
"""Puts mujoco.MjModel onto a device, resulting in mjx.Model."""
|
||||||
if not mjxw.WARP_INSTALLED:
|
|
||||||
raise RuntimeError('Warp not installed.')
|
|
||||||
|
|
||||||
with wp.ScopedDevice('cpu'): # pylint: disable=undefined-variable
|
with wp.ScopedDevice('cpu'): # pylint: disable=undefined-variable
|
||||||
mw = mjwp.put_model(m) # pylint: disable=undefined-variable
|
mw = mjwp.put_model(m) # pylint: disable=undefined-variable
|
||||||
|
|
||||||
@@ -464,7 +464,10 @@ def _put_model_warp(
|
|||||||
option_keys = {f.name for f in mjxw.types.OptionWarp.fields()} - {
|
option_keys = {f.name for f in mjxw.types.OptionWarp.fields()} - {
|
||||||
f.name for f in types.Option.fields()
|
f.name for f in types.Option.fields()
|
||||||
}
|
}
|
||||||
|
# graph_mode is MJX-specific, not from mujoco.mjx.third_party.mujoco_warp.
|
||||||
|
option_keys = option_keys - {'graph_mode'}
|
||||||
private_options = {k: getattr(mw.opt, k) for k in option_keys}
|
private_options = {k: getattr(mw.opt, k) for k in option_keys}
|
||||||
|
private_options['graph_mode'] = graph_mode
|
||||||
fields['opt'] = _put_option(m.opt, types.Impl.WARP, private_options)
|
fields['opt'] = _put_option(m.opt, types.Impl.WARP, private_options)
|
||||||
fields['stat'] = _put_statistic(m.stat, types.Impl.WARP)
|
fields['stat'] = _put_statistic(m.stat, types.Impl.WARP)
|
||||||
|
|
||||||
@@ -527,6 +530,7 @@ def put_model(
|
|||||||
m: mujoco.MjModel,
|
m: mujoco.MjModel,
|
||||||
device: Optional[jax.Device] = None,
|
device: Optional[jax.Device] = None,
|
||||||
impl: Optional[Union[str, types.Impl]] = None,
|
impl: Optional[Union[str, types.Impl]] = None,
|
||||||
|
graph_mode: Optional[mjxw.types.GraphMode] = None,
|
||||||
) -> types.Model:
|
) -> types.Model:
|
||||||
"""Puts mujoco.MjModel onto a device, resulting in mjx.Model.
|
"""Puts mujoco.MjModel onto a device, resulting in mjx.Model.
|
||||||
|
|
||||||
@@ -534,12 +538,15 @@ def put_model(
|
|||||||
m: the model to put onto device
|
m: the model to put onto device
|
||||||
device: which device to use - if unspecified picks the default device
|
device: which device to use - if unspecified picks the default device
|
||||||
impl: implementation to use
|
impl: implementation to use
|
||||||
|
graph_mode: CUDA graph capture mode (for Warp only). Use GraphMode enum from
|
||||||
|
warp._src.jax_experimental.ffi. GraphMode.WARP is the default mode.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
an mjx.Model placed on device
|
an mjx.Model placed on device
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: if impl is not supported
|
ValueError: if impl is not supported
|
||||||
|
RuntimeError: if impl is WARP and warp-lang is not installed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
impl, device = _resolve_impl_and_device(impl, device)
|
impl, device = _resolve_impl_and_device(impl, device)
|
||||||
@@ -548,7 +555,9 @@ def put_model(
|
|||||||
elif impl == types.Impl.C:
|
elif impl == types.Impl.C:
|
||||||
return _put_model_c(m, device)
|
return _put_model_c(m, device)
|
||||||
elif impl == types.Impl.WARP:
|
elif impl == types.Impl.WARP:
|
||||||
return _put_model_warp(m, device)
|
_check_warp_installed()
|
||||||
|
graph_mode = graph_mode or getattr(mjxw.types.GraphMode, 'WARP')
|
||||||
|
return _put_model_warp(m, graph_mode, device)
|
||||||
elif impl == types.Impl.CPP:
|
elif impl == types.Impl.CPP:
|
||||||
return _put_model_cpp(m, device)
|
return _put_model_cpp(m, device)
|
||||||
else:
|
else:
|
||||||
@@ -861,9 +870,6 @@ def _make_data_warp(
|
|||||||
f' {type(m)}.'
|
f' {type(m)}.'
|
||||||
)
|
)
|
||||||
|
|
||||||
if not mjxw.WARP_INSTALLED:
|
|
||||||
raise RuntimeError('Warp is not installed.')
|
|
||||||
|
|
||||||
with wp.ScopedDevice('cpu'): # pylint: disable=undefined-variable
|
with wp.ScopedDevice('cpu'): # pylint: disable=undefined-variable
|
||||||
dw = mjwp.make_data(m, nworld=1, naconmax=naconmax, njmax=njmax) # pylint: disable=undefined-variable
|
dw = mjwp.make_data(m, nworld=1, naconmax=naconmax, njmax=njmax) # pylint: disable=undefined-variable
|
||||||
|
|
||||||
@@ -1003,6 +1009,7 @@ def make_data(
|
|||||||
elif impl == types.Impl.CPP:
|
elif impl == types.Impl.CPP:
|
||||||
return _make_data_cpp(m, device)
|
return _make_data_cpp(m, device)
|
||||||
elif impl == types.Impl.WARP:
|
elif impl == types.Impl.WARP:
|
||||||
|
_check_warp_installed()
|
||||||
naconmax = nconmax if naconmax is None else naconmax
|
naconmax = nconmax if naconmax is None else naconmax
|
||||||
return _make_data_warp(m, device, naconmax, njmax)
|
return _make_data_warp(m, device, naconmax, njmax)
|
||||||
|
|
||||||
|
|||||||
@@ -334,6 +334,25 @@ class ModelIOTest(parameterized.TestCase):
|
|||||||
|
|
||||||
_ = jax.tree.map_with_path(check_ndim, mx)
|
_ = jax.tree.map_with_path(check_ndim, mx)
|
||||||
|
|
||||||
|
@parameterized.parameters('JAX', 'WARP', None)
|
||||||
|
def test_put_model_warp_graph_mode(self, mode: str | None):
|
||||||
|
"""Tests that put_model accepts graph_mode parameter."""
|
||||||
|
if not mjxw.WARP_INSTALLED:
|
||||||
|
self.skipTest('Warp not installed.')
|
||||||
|
if not mjx_io.has_cuda_gpu_device():
|
||||||
|
self.skipTest('No CUDA GPU device available.')
|
||||||
|
|
||||||
|
if mode is None:
|
||||||
|
graph_mode = None
|
||||||
|
else:
|
||||||
|
graph_mode = getattr(mjxw_types.GraphMode, mode)
|
||||||
|
|
||||||
|
m = mujoco.MjModel.from_xml_string(_SIMPLE_BODY)
|
||||||
|
mx = mjx.put_model(m, impl='warp', graph_mode=graph_mode)
|
||||||
|
|
||||||
|
expected = graph_mode or mjxw_types.GraphMode.WARP
|
||||||
|
self.assertEqual(mx.opt._impl.graph_mode, expected)
|
||||||
|
|
||||||
@parameterized.parameters('c', 'jax')
|
@parameterized.parameters('c', 'jax')
|
||||||
def test_unsupported_contact_types(self, impl):
|
def test_unsupported_contact_types(self, impl):
|
||||||
"""Tests that unsupported contact types raise an error."""
|
"""Tests that unsupported contact types raise an error."""
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ _e = mjwarp.Constraint(
|
|||||||
**{f.name: None for f in dataclasses.fields(mjwarp.Constraint) if f.init}
|
**{f.name: None for f in dataclasses.fields(mjwarp.Constraint) if f.init}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ffi.format_args_for_warp
|
@ffi.format_args_for_warp
|
||||||
def _collision_shim(
|
def _collision_shim(
|
||||||
# Model
|
# Model
|
||||||
@@ -279,6 +278,7 @@ def _collision_jax_impl(m: types.Model, d: types.Data):
|
|||||||
'contact__type',
|
'contact__type',
|
||||||
'contact__worldid',
|
'contact__worldid',
|
||||||
},
|
},
|
||||||
|
graph_mode=m.opt._impl.graph_mode,
|
||||||
)
|
)
|
||||||
out = jf(
|
out = jf(
|
||||||
d.qpos.shape[0],
|
d.qpos.shape[0],
|
||||||
|
|||||||
@@ -1220,6 +1220,7 @@ def _forward_jax_impl(m: types.Model, d: types.Data):
|
|||||||
'efc__type',
|
'efc__type',
|
||||||
'efc__vel',
|
'efc__vel',
|
||||||
},
|
},
|
||||||
|
graph_mode=m.opt._impl.graph_mode,
|
||||||
)
|
)
|
||||||
out = jf(
|
out = jf(
|
||||||
d.qpos.shape[0],
|
d.qpos.shape[0],
|
||||||
@@ -3018,6 +3019,7 @@ def _step_jax_impl(m: types.Model, d: types.Data):
|
|||||||
'efc__type',
|
'efc__type',
|
||||||
'efc__vel',
|
'efc__vel',
|
||||||
},
|
},
|
||||||
|
graph_mode=m.opt._impl.graph_mode,
|
||||||
)
|
)
|
||||||
out = jf(
|
out = jf(
|
||||||
d.qpos.shape[0],
|
d.qpos.shape[0],
|
||||||
|
|||||||
@@ -216,8 +216,11 @@ class StepTest(parameterized.TestCase):
|
|||||||
'pendula.xml',
|
'pendula.xml',
|
||||||
),
|
),
|
||||||
batch_size=(1, 7),
|
batch_size=(1, 7),
|
||||||
|
# NOTE: GraphMode.JAX is incompatible with MuJoCo Warp at the moment,
|
||||||
|
# even when setting graph_conditional=False.
|
||||||
|
graph_mode=('WARP',),
|
||||||
)
|
)
|
||||||
def test_step(self, xml: str, batch_size: int):
|
def test_step(self, xml: str, batch_size: int, graph_mode: str):
|
||||||
if not _FORCE_TEST:
|
if not _FORCE_TEST:
|
||||||
if not mjxw.WARP_INSTALLED:
|
if not mjxw.WARP_INSTALLED:
|
||||||
self.skipTest('Warp not installed.')
|
self.skipTest('Warp not installed.')
|
||||||
@@ -227,7 +230,9 @@ class StepTest(parameterized.TestCase):
|
|||||||
m = test_util.load_test_file(xml)
|
m = test_util.load_test_file(xml)
|
||||||
m.opt.iterations = 10
|
m.opt.iterations = 10
|
||||||
m.opt.ls_iterations = 10
|
m.opt.ls_iterations = 10
|
||||||
mx = mjx.put_model(m, impl='warp')
|
mx = mjx.put_model(
|
||||||
|
m, impl='warp', graph_mode=getattr(mjxw.types.GraphMode, graph_mode)
|
||||||
|
)
|
||||||
|
|
||||||
d = mujoco.MjData(m)
|
d = mujoco.MjData(m)
|
||||||
worldids = jp.arange(batch_size)
|
worldids = jp.arange(batch_size)
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ def _kinematics_jax_impl(m: types.Model, d: types.Data):
|
|||||||
'xpos',
|
'xpos',
|
||||||
'xquat',
|
'xquat',
|
||||||
},
|
},
|
||||||
|
graph_mode=m.opt._impl.graph_mode,
|
||||||
)
|
)
|
||||||
out = jf(
|
out = jf(
|
||||||
d.qpos.shape[0],
|
d.qpos.shape[0],
|
||||||
@@ -379,6 +380,7 @@ def _tendon_jax_impl(m: types.Model, d: types.Data):
|
|||||||
'wrap_obj',
|
'wrap_obj',
|
||||||
'wrap_xpos',
|
'wrap_xpos',
|
||||||
},
|
},
|
||||||
|
graph_mode=m.opt._impl.graph_mode,
|
||||||
)
|
)
|
||||||
out = jf(
|
out = jf(
|
||||||
d.qpos.shape[0],
|
d.qpos.shape[0],
|
||||||
|
|||||||
@@ -16,12 +16,21 @@
|
|||||||
DO NOT EDIT. This file is auto-generated.
|
DO NOT EDIT. This file is auto-generated.
|
||||||
"""
|
"""
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
import typing
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
import jax
|
import jax
|
||||||
from jax import tree_util
|
from jax import tree_util
|
||||||
from jax.interpreters import batching
|
from jax.interpreters import batching
|
||||||
from mujoco.mjx._src import dataclasses as mjx_dataclasses
|
from mujoco.mjx._src import dataclasses as mjx_dataclasses
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
GraphMode = int
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
from warp._src.jax_experimental.ffi import GraphMode
|
||||||
|
except ImportError:
|
||||||
|
GraphMode = int
|
||||||
PyTreeNode = mjx_dataclasses.PyTreeNode
|
PyTreeNode = mjx_dataclasses.PyTreeNode
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
@@ -35,7 +44,6 @@ class TileSet:
|
|||||||
adr: address of each tile in the set
|
adr: address of each tile in the set
|
||||||
size: size of all the tiles in this set
|
size: size of all the tiles in this set
|
||||||
"""
|
"""
|
||||||
|
|
||||||
adr: np.ndarray
|
adr: np.ndarray
|
||||||
size: int
|
size: int
|
||||||
|
|
||||||
@@ -95,6 +103,7 @@ class OptionWarp(PyTreeNode):
|
|||||||
ccd_tolerance: jax.Array
|
ccd_tolerance: jax.Array
|
||||||
contact_sensor_maxmatch: int
|
contact_sensor_maxmatch: int
|
||||||
graph_conditional: bool
|
graph_conditional: bool
|
||||||
|
graph_mode: GraphMode
|
||||||
has_fluid: bool
|
has_fluid: bool
|
||||||
impratio_invsqrt: jax.Array
|
impratio_invsqrt: jax.Array
|
||||||
is_sparse: bool
|
is_sparse: bool
|
||||||
|
|||||||
Reference in New Issue
Block a user