Skip to content

MJX-Warp: UnexpectedTracerError in jnp.where-based auto-reset function on Warp data #3377

Description

@danielpmorton

Intro

Hey MuJoCo team,

Thanks for the great work on MJWarp! I've been messing around with MJX-Warp and it's generally been working well for me. I ran into a tricky tracer leak issue earlier today, and I'm unsure if it's a bug, or if I'm misunderstanding how to design the JAX interface with the Warp data.

Here's a reproducer script that leads to the UnexpectedTracerError in an "autoreset" style interface similar to other RL pipelines. (Looking at BRAX and Playground has been super helpful!) This occurs when jnp.where is called between the reset data and the stepped data, and I'm not quite sure if it's the where or the vmap that is the source of the error.

Disclaimer: Claude helped me track down this error. I still consider myself to be an MJWarp beginner, so bear with me if I'm misunderstanding something fundamental.

My setup

Here's what I'm currently running in my venv:

jax[cuda13]==0.10.2
mujoco == 3.10.0
mujoco-mjx == 3.10.0
warp-lang == 1.13.0

Python: 3.12.3
GPU: RTX 5000 (laptop) with CUDA 13.0
OS: Ubuntu 24.04

What's happening? What did you expect?

[described above]

Steps for reproduction

Here's a reproducer script

import jax
import jax.numpy as jnp
import mujoco
from mujoco import mjx

# Simple setup with a box initialized in the air above a flat plane
XML = """
<mujoco>
  <option timestep="0.005"/>
  <worldbody>
    <geom name="floor" type="plane" size="5 5 0.1"/>
    <body name="box" pos="0 0 0.2">
      <freejoint/>
      <geom name="box" type="box" size="0.1 0.1 0.1"/>
    </body>
  </worldbody>
</mujoco>
"""


# Flag to apply a somewhat hacky "fix" to this issue
# This was discovered by Claude but it does not really feel like a great solution
# If True, this will always maintain the _impl field of the stepped env rather than the reset env.
# I believe (maybe) this doesn't cause issues if this data is recomputed?
# But also I'm wondering if there is ever a case where the wrong data is applied immediately after a reset
KEEP_STEPPED_IMPL = False


def make_reset(mjx_model, data_template):
    def reset(key):
        # reset the box height to a somewhat random value
        z = 0.2 + 0.1 * jax.random.uniform(key)
        qpos = data_template.qpos.at[2].set(z)
        d = data_template.replace(qpos=qpos)
        return mjx.forward(mjx_model, d)

    return reset


def make_autoreset_step(mjx_model, reset_fn):
    def step(key, data):
        stepped_data = mjx.step(mjx_model, data)
        # terminate when the box has fallen below a height (just for example purposes)
        done = stepped_data.qpos[2] < 0.15

        reset_data = reset_fn(key)

        next_data = jax.tree_util.tree_map(
            lambda r, s: jnp.where(done, r, s), reset_data, stepped_data
        )

        if KEEP_STEPPED_IMPL:  # Hacky "fix" described above
            next_data = next_data.replace(_impl=stepped_data._impl)

        return next_data

    return step


def main():
    NUM_ENVS = 8
    NUM_STEPS = 4

    mj_model = mujoco.MjModel.from_xml_string(XML)
    mjx_model = mjx.put_model(mj_model, impl="warp")
    data_template = mjx.make_data(
        mj_model, impl="warp", naconmax=16 * NUM_ENVS, njmax=64
    )
    reset_fn = make_reset(mjx_model, data_template)
    step_fn = make_autoreset_step(mjx_model, reset_fn)

    keys = jax.random.split(jax.random.PRNGKey(0), NUM_ENVS)
    batched_data = jax.vmap(reset_fn)(keys)

    def rollout(data, key):
        def body(carry, _):
            data, key = carry
            key, sk = jax.random.split(key)
            step_keys = jax.random.split(sk, NUM_ENVS)
            data = jax.vmap(step_fn)(step_keys, data)
            return (data, key), data.qpos[:, 2]

        return jax.lax.scan(body, (data, key), None, length=NUM_STEPS)

    # This will fail with an UnexpectedTracerError if KEEP_STEPPED_IMPL = False
    out = jax.jit(rollout)(batched_data, jax.random.PRNGKey(1))


if __name__ == "__main__":
    main()

Minimal model for reproduction

No response

Code required for reproduction

No response

Confirmations

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions