continuation value.

August 16, 2026 ยท View on GitHub


Reinforcement Learning Environments in JAX ๐ŸŒ

Are you fed up with slow CPU-based RL environment processes? Do you want to leverage massive vectorization for high-throughput RL experiments? gymnax brings the power of jit and vmap/pmap to the classic gym API. It supports a range of different environments including classic control, bsuite, MinAtar and a collection of classic/meta RL tasks. gymnax allows explicit functional control of environment settings (random seed or hyperparameters), which enables accelerated & parallelized rollouts for different configurations (e.g. for meta RL). By executing both environment and policy on the accelerator, it facilitates the Anakin sub-architecture proposed in the Podracer paper (Hessel et al., 2021) and highly distributed evolutionary optimization (using e.g. evosax). We provide training & checkpoints for both PPO & ES in gymnax-blines. Get started here ๐Ÿ‘‰ Colab.

Upgrading to Gymnax 1.0

Important

Gymnax 1.0 has a breaking step API.

  • env.step(...) now returns (observation, state, reward, terminated, truncated, info), replacing the former five-value return with done.
  • For episode control, use done = terminated | truncated. For value bootstrapping, only terminated means zero continuation value.
  • Terminal steps auto-reset; use info["final_observation"] for the pre-reset terminal observation.
  • Custom environments should implement observe(key, state, action, params).

Existing five-value callers can use gymnax.wrappers.LegacyStepAPIWrapper while migrating. See the API and compatibility RFC for details.

Basic gymnax API Usage ๐Ÿฒ

import jax
import jax.numpy as jnp
import gymnax

key = jax.random.key(0)
key, key_reset, key_act, key_step = jax.random.split(key, 4)

# Instantiate the environment & its settings.
env, env_params = gymnax.make("Pendulum-v1")

# Reset the environment.
obs, state = env.reset(key_reset, env_params)

# Sample a random action.
action = env.action_space(env_params).sample(key_act)

# Perform the step transition. Gymnax auto-resets after either terminal cause.
n_obs, n_state, reward, terminated, truncated, info = env.step(
    key_step, state, action, env_params
)

# Bootstrap from the pre-reset observation; only natural termination has zero
# continuation value.
bootstrap_obs = info["final_observation"]
bootstrap_mask = 1.0 - info["terminated"].astype(jnp.float32)

spaces.Discrete(n, dtype=...) uses int32 actions by default. int64 is available only when JAX x64 mode is enabled; other dtypes are unsupported.

Differentiable transitions

Built-in environments detach transition observations and states by default. Continuous-control applications can opt into JAX gradients without changing the original environment:

env, env_params = gymnax.make("Pendulum-v1")
differentiable_env = env.with_transition_gradients()
obs, state = differentiable_env.reset(key_reset, env_params)

def next_angular_velocity(action):
    return differentiable_env.step(key_step, state, action, env_params)[1].theta_dot

action = jnp.array([0.5])
transition_gradient = jax.grad(next_angular_velocity)(action)

with_transition_gradients() returns a configured copy, leaving env unchanged. It is supported by Pendulum-v1, MountainCarContinuous-v0, PointRobot-misc, Reacher-misc, and Swimmer-misc. Configure the environment before applying wrappers. Discrete-action environments such as CartPole-v1 reject this opt-in because action derivatives are not part of their contract.

These are local, pathwise derivatives of the JAX program for a fixed random key. Clipping boundaries, comparisons, terminal decisions, and discrete events are not smooth; PointRobot's goal-triggered respawn is also discontinuous. Differentiate floating-point state leaves rather than the complete state PyTree, which includes integer bookkeeping such as time.

Automatic reset behavior is unchanged. On a terminal or truncated step, the returned observation and state belong to the reset episode and therefore do not carry the completed transition's action gradient. The gradient remains available through info["final_observation"]; use step_env when the raw, non-autoreset next state is required. Reward gradients are unaffected by the opt-in and remain available wherever the reward implementation is differentiable.

Implemented Accelerated Environments ๐ŸŽ๏ธ

Environment NameReferenceSource๐Ÿค– Ckpt (Return)Secs/1M ๐Ÿฆถ
A100 (2k ๐ŸŒŽ)
Acrobot-v1Brockman et al. (2016)ClickPPO, ES (R: -80)0.07
Pendulum-v1Brockman et al. (2016)ClickPPO, ES (R: -130)0.07
CartPole-v1Brockman et al. (2016)ClickPPO, ES (R: 500)0.05
MountainCar-v0Brockman et al. (2016)ClickPPO, ES (R: -118)0.07
MountainCarContinuous-v0Brockman et al. (2016)ClickPPO, ES (R: 92)0.09
Asterix-MinAtarYoung & Tian (2019)ClickPPO (R: 15)0.92
Breakout-MinAtarYoung & Tian (2019)ClickPPO (R: 28)0.19
Freeway-MinAtarYoung & Tian (2019)ClickPPO (R: 58)0.87
Seaquest-MinAtarYoung & Tian (2019)Click--
SpaceInvaders-MinAtarYoung & Tian (2019)ClickPPO (R: 131)0.33
Catch-bsuiteOsband et al. (2019)ClickPPO, ES (R: 1)0.15
DeepSea-bsuiteOsband et al. (2019)ClickPPO, ES (R: 0)0.22
MemoryChain-bsuiteOsband et al. (2019)ClickPPO, ES (R: 0.1)0.13
UmbrellaChain-bsuiteOsband et al. (2019)ClickPPO, ES (R: 1)0.08
DiscountingChain-bsuiteOsband et al. (2019)ClickPPO, ES (R: 1.1)0.06
MNISTBandit-bsuiteOsband et al. (2019)Click--
SimpleBandit-bsuiteOsband et al. (2019)Click--
FourRooms-miscSutton et al. (1999)ClickPPO, ES (R: 1)0.07
MetaMaze-miscMicconi et al. (2020)ClickES (R: 32)0.09
PointRobot-miscDorfman et al. (2021)ClickES (R: 10)0.08
BernoulliBandit-miscWang et al. (2017)ClickES (R: 90)0.08
GaussianBandit-miscLange & Sprekeler (2022)ClickES (R: 0)0.07
Reacher-miscLenton et al. (2021)Click
Swimmer-miscLenton et al. (2021)Click
Pong-miscKirsch (2018)Click
FrozenLake-miscBrockman et al. (2016)Gymnasium FrozenLake

* All displayed speeds are estimated for 1M step transitions (random policy) on a NVIDIA A100 GPU using jit compiled episode rollouts with 2000 environment workers. For more detailed speed comparisons on different accelerators (CPU, RTX 2080Ti) and MLP policies, please refer to the gymnax-blines documentation.

Installation โณ

The latest gymnax release can directly be installed from PyPI:

pip install gymnax

If you want to get the most recent commit, please install directly from the repository:

pip install git+https://github.com/RobertTLange/gymnax.git@main

In order to use JAX on your accelerators, you can find more details in the JAX documentation.

Supported versions

Gymnax supports CPython 3.10โ€“3.13, JAX and JAXlib 0.6.x, and Gymnasium 1.1.x. Other dependency versions may work, but are not part of the tested support matrix.

Examples ๐Ÿ“–

Custom environments

Register a factory to make a custom environment available through the usual gymnax.make entry point. The factory receives any make keyword arguments and must create a fresh Environment instance:

import gymnax
from my_package import MyEnvironment

gymnax.register("MyEnvironment-v0", MyEnvironment)
env, params = gymnax.make("MyEnvironment-v0", difficulty="hard")

Registration is process-global for the running Python interpreter; choose a unique ID and register each factory once during application setup.

Pixel-style observations

Pixel-style observations are multi-channel tensors, not renderer images. Asterix-MinAtar, Breakout-MinAtar, Freeway-MinAtar, Seaquest-MinAtar, and SpaceInvaders-MinAtar provide MinAtar grid tensors. Pong-misc provides a three-channel grid; FourRooms-misc provides a (13, 13, 2) grid when constructed with use_visual_obs=True. For visualization, use the environment-specific render(state, params) method where it is implemented.

Key Selling Points ๐Ÿ’ต

  • Environment vectorization & acceleration: Easy composition of JAX primitives (e.g. jit, vmap, pmap):

    # Jit-accelerated step transition
    jit_step = jax.jit(env.step)
    
    # map (vmap/pmap) across random keys for batch rollouts
    reset_key = jax.vmap(env.reset, in_axes=(0, None))
    step_key = jax.vmap(env.step, in_axes=(0, 0, 0, None))
    
    # map (vmap/pmap) across env parameters (e.g. for meta-learning)
    reset_params = jax.vmap(env.reset, in_axes=(None, 0))
    step_params = jax.vmap(env.step, in_axes=(None, 0, 0, 0))
    

    For speed comparisons with standard vectorized NumPy environments check out gymnax-blines.

  • Scan through entire episode rollouts: You can also lax.scan through entire reset, step episode loops for fast compilation:

    def rollout(key_input, policy_params, env_params, steps_in_episode):
        """Rollout a jitted gymnax episode with lax.scan."""
        # Reset the environment
        key_reset, key_episode = jax.random.split(key_input)
        obs, state = env.reset(key_reset, env_params)
    
        def policy_step(state_input, tmp):
            """lax.scan compatible step transition in jax env."""
            obs, state, policy_params, key = state_input
            key, key_step, key_net = jax.random.split(key, 3)
            action = model.apply(policy_params, obs)
            next_obs, next_state, reward, terminated, truncated, _ = env.step(
                key_step, state, action, env_params
            )
            done = terminated | truncated
            carry = [next_obs, next_state, policy_params, key]
            return carry, [obs, action, reward, next_obs, done]
    
        # Scan over episode step loop
        _, scan_out = jax.lax.scan(
            policy_step,
            [obs, state, policy_params, key_episode],
            (),
            steps_in_episode
        )
        # Return masked sum of rewards accumulated by agent in episode
        obs, action, reward, next_obs, done = scan_out
        return obs, action, reward, next_obs, done
    
  • Built-in visualization tools: Generate GIF animations with the Visualizer tool. Install the optional classic-control renderer first with pip install "gymnax[visualize]"; GIF export uses Pillow and does not require ffmpeg. The visualizer supports the Gymnasium classic-control environments and selected native MinAtar and misc environments:

    from gymnax.visualize import Visualizer
    
    state_seq, reward_seq = [], []
    key, key_reset = jax.random.split(key)
    obs, env_state = env.reset(key_reset, env_params)
    while True:
        state_seq.append(env_state)
        key, key_act, key_step = jax.random.split(key, 3)
        action = env.action_space(env_params).sample(key_act)
        next_obs, next_env_state, reward, terminated, truncated, info = env.step(
            key_step, env_state, action, env_params
        )
        done = terminated | truncated
        reward_seq.append(reward)
        if done:
            break
        else:
          obs = next_obs
          env_state = next_env_state
    
    cum_rewards = jnp.cumsum(jnp.array(reward_seq))
    vis = Visualizer(env, env_params, state_seq, cum_rewards)
    vis.animate(f"docs/anim.gif")
    

    Wrap a discrete-action environment with opt-in sticky actions when an experiment needs action persistence:

    from gymnax.wrappers import StickyActionWrapper
    
    env = StickyActionWrapper(env, sticky_action_prob=0.1)
    

    For a transitional five-value API, opt in explicitly:

    from gymnax.wrappers import LegacyStepAPIWrapper
    
    legacy_env = LegacyStepAPIWrapper(env)
    next_obs, next_state, reward, done, info = legacy_env.step(
        key_step, state, action, env_params
    )
    

    Native env.render(state, params) methods return Matplotlib figures and axes for environment-specific debugging. To display one interactively, use an interactive Matplotlib backend and call plt.show().

  • Training pipelines & pretrained agents: Check out gymnax-blines for trained agents, expert rollout visualizations and PPO/ES pipelines. The agents are minimally tuned, but can help you get up and running.

  • Simple batch agent evaluation: Work-in-progress.

    from gymnax.experimental import RolloutWrapper
    
    # Define rollout manager for pendulum env
    manager = RolloutWrapper(model.apply, env_name="Pendulum-v1")
    
    # Simple single episode rollout for policy
    obs, action, reward, next_obs, done, cum_ret = manager.single_rollout(key, policy_params)
    
    # Multiple rollouts for same network (different key, e.g. eval)
    key_batch = jax.random.split(key, 10)
    obs, action, reward, next_obs, done, cum_ret = manager.batch_rollout(
        key_batch, policy_params
    )
    
    # Multiple rollouts for different networks + key (e.g. for ES)
    batch_params = jax.tree.map(  # Stack parameters or use different
        lambda x: jnp.tile(x, (5, 1)).reshape(5, *x.shape), policy_params
    )
    obs, action, reward, next_obs, done, cum_ret = manager.population_rollout(
        key_batch, batch_params
    )
    

Resources & Other Great Tools ๐Ÿ“

  • ๐Ÿ’ป Brax: JAX-based library for rigid body physics by Google Brain with JAX-style MuJoCo substitutes.
  • ๐Ÿ’ป envpool: Vectorized parallel environment execution engine.
  • ๐Ÿ’ป Jumanji: A suite of diverse and challenging RL environments in JAX.
  • ๐Ÿ’ป Pgx: JAX-based classic board game environments.

Acknowledgements & Citing gymnax โœ๏ธ

If you use gymnax in your research, please cite it as follows:

@software{gymnax2022github,
  author = {Robert Tjarko Lange},
  title = {{gymnax}: A {JAX}-based Reinforcement Learning Environment Library},
  url = {http://github.com/RobertTLange/gymnax},
  version = {1.0.0},
  year = {2026},
}

We acknowledge financial support by the Google TRC and the Deutsche Forschungsgemeinschaft (DFG, German Research Foundation) under Germany's Excellence Strategy - EXC 2002/1 "Science of Intelligence" - project number 390523135.

Development ๐Ÿ‘ท

Install the locked development and test environment with uv sync --locked --all-extras. Run uv run ruff check ., uv run ruff format --check ., and uv run pytest -vv --all before opening a pull request.

When running the test suite, it is strongly encouraged, but not required, to treat warnings as errors with uv run pytest -vv -W error --tb=short --all. If you find a bug or are missing your favourite feature, feel free to create an issue and/or start contributing ๐Ÿค—.