T5 — Run on GPU / MPS¶
Install GPU- or MPS-backed JAX and vmap a rollout over a batch of seeds. The realistic perf win for this codebase is parallel-over-seeds, not single-rollout speedup. Builds on T1 — First rollout (RT 2D).
Install¶
For NVIDIA CUDA:
For Apple Silicon Metal (MPS):
These extras pull in the matching jax-with-device wheel.
Verify the device¶
import jax
import jax.numpy as jnp
from orbitalgym import (
OrbitalGymEnv,
SingleAgentView,
make_pursuit_evasion,
)
from orbitalgym.policies import ZeroControl
from orbitalgym.rollout import rollout_single_agent
You should see CudaDevice(id=0) (CUDA) or MetalDevice(id=0) (MPS).
If you see only CpuDevice, the extra didn't take — check
pip show jax and pip show jaxlib for matching device-suffixed
versions.
A single rollout often loses on GPU¶
The whole rollout_single_agent already compiles to one
jax.lax.scan. Single-rollout dispatch over PCIe / Metal Shared
Memory typically loses to CPU in-process for small problem sizes. Run
a benchmark before assuming the GPU helps.
The headline: vmap over seeds¶
The realistic speedup comes from running many rollouts in parallel.
vmap over the seed axis adds a leading batch dimension to every leaf
of the resulting Trajectory:
def run_one(seed: jax.Array):
return rollout_single_agent(
view,
guard_policy,
lambda c, s, k: None,
seed,
n_steps=cfg.max_steps,
)
n_seeds = 32 # 1024 in real use; small here for CI speed
seeds = jax.vmap(jax.random.PRNGKey)(jnp.arange(n_seeds))
trajs = jax.jit(jax.vmap(run_one))(seeds)
After the vmap:
trajs.sides.guard.rewardhas shape(B, T).trajs.env_state.guards.rtnhas shape(B, T, N_g, 6).- The full
BATCH → TIME → VEHICLE → FEATUREaxis order applies.
Read off batched results¶
# Leading axis is now the seed batch:
rewards = trajs.sides.guard.reward # (B, T)
final_rewards = rewards.sum(axis=-1) # (B,)
mean_return = float(final_rewards.mean())
final_rewards has shape (B,) — the cumulative reward of each
seed's episode. final_rewards.std() is your unbiased estimate of
return variance, useful for plotting confidence bands.
Caveats¶
- Float64 is the default in this package (set at import time). For throughput-focused experiments — and as a hard requirement on Apple Metal / MPS, which is float32-only — flip with the package helper:
This sets both astrojax's internal dtype and JAX's jax_enable_x64
flag in lockstep (astrojax.config.set_dtype alone does not toggle
jax_enable_x64 back to False, so calling it directly leaves the
package in an inconsistent state). Verify your physics still produces
sensible trajectories at single precision — at 7000 km Earth-orbit
scale, float32 has ~0.5 m worst-case position precision. Build any envs
after the precision flip — typed-instance dynamics (e.g.
KeplerianEciDynamics) capture their dtype in __post_init__.
- MPS is not always faster. For workloads with many small per-step JAX dispatches (e.g. a Python-loop tree-search planner), MPS dispatch overhead dominates over actual compute and can run slower than CPU. The MPS speedup story is best when each compiled call does enough work to amortize dispatch — vmap over hundreds of trajectories and large state vectors. Always benchmark before declaring victory.
- JIT cache warmup — the first call to a
jax.jit-compiled function compiles it. Time a second call for the steady-state number. - Host transfer cost —
float(arr)blocks until GPU→CPU transfer completes. Pull arrays to host once, after the loop.
What's next¶
You've completed the tutorial track. From here:
- Browse Game guides for what each game rewards.
- Use a how-to recipe: vmap rollouts over seeds is the one-page reference for the pattern above.
- Read In depth → Symmetric core for the full type-and-shape model.