Run on GPU¶
Pass --device gpu (CLI) or device="gpu" (Python) to place a solve on an
accelerator; the default auto applies a measured policy that picks the GPU
only where it wins — per-iteration work ns*mnmax*nznt >= 100_000 and at
most 512 active Fourier modes, where the measured advantage is 2-3x wall
(benchmarks/gpu_baseline.json).
Select the device¶
vmex input.case --device gpu # explicit: always wins
vmex input.case --device cpu
vmex input.case --device auto # default: measured policy
vmex input.case --device none # leave placement to JAX
import vmex as vj
result = vj.solve_multigrid(inp, device="gpu")
Explicit device= always wins. auto stands down when you pinned a JAX
default device or platform yourself (jax.config.update("jax_default_device", ...),
JAX_PLATFORMS, jax.default_device(...)), so VMEX never fights your
placement. Install notes for GPU wheels: Installation.
When the GPU pays off¶
The policy in vmex.core.device is measured, not guessed
(benchmarks/gpu_baseline.json; regenerate with
benchmarks/device_parity.py and the benchmark scripts):
Per-iteration throughput favors the GPU — up to 3x wall on NuhrenbergZille-class decks — but the GPU pays fixed per-solve overheads (~0.2-0.4 s dispatch/transfer floor plus compile/cache-load on cold processes), so small decks that converge in under a second of CPU work finish faster on the CPU.
The work proxy is
ns * mnmax * nznt(radial surfaces x spectral modes x angular grid — the cost driver of the batchedtotzsps/tomnspsmatmuls). Measured decks split into two clusters: proxies up to ~24e3 where the CPU wins (and misclassification costs < 0.5 s either way), and >= ~490e3 where the GPU wins 2-3x.GPU_MIN_ITERATION_WORK = 100_000sits between them (geometric mean ~109e3). The range between the clusters is not calibrated.Mode count is an independent guard: the measured GPU winners have at most 162 active modes, while a high-resolution HSX deck (
mnmax=858) ran ~3.4x slower on the GPU even warm despite a large work proxy.GPU_MAX_SPECTRAL_MODES = 512sits between the largest measured GPU winner (288 modes) and that high-mode CPU winner; the cutoff is not claimed as a hardware-independent crossover.
Ask the policy directly:
from vmex.core.device import GPU_MIN_ITERATION_WORK, iteration_work, recommended_device
print(iteration_work(runtime.resolution), GPU_MIN_ITERATION_WORK)
print(recommended_device(runtime.resolution)) # "cpu" or "gpu"
What stays on CPU regardless¶
Ensembles. Multi-solve ensembles are CPU-threaded (Solve many equilibria at once): the host solver’s
pure_callbackcannot run on a GPU.Implicit Jacobians in optimization. High-level optimization defaults its implicit-gradient path to CPU because it is launch-bound on the tested GPUs; low-level
vmex.core.implicit.run()follows JAX placement whendeviceis omitted, and acceptsdevice="gpu"plusdevice_scope()for explicit accelerator gradients.The dense NESTOR factor. On a GPU free-boundary run the plasma iteration stays on the accelerator while the dense vacuum assembly/factor/solve is explicitly placed on CPU (The NESTOR vacuum solve).
Verify what you got¶
vmex --doctor
prints the JAX backend, visible devices, the active default device, and
VMEX’s forward/implicit placement policies. It also executes a small float64
JIT calculation on the selected JAX device; on WSL2 it reports the NVIDIA GPU
and Windows driver visible through nvidia-smi. See Installation for
the upstream fixes to the warnings seen with JAX 0.9.2 and the required
JAX/jaxlib upgrade. Per-deck CPU-vs-GPU timings and the decision sweep for a
new machine are in Performance and validation.
Separate cold compile, cache reload, and warm execution¶
For a new GPU or a slow installation, run the cache-reload audit from a VMEX source checkout:
python benchmarks/device_cache_reload.py --devices cpu,gpu \
--output vmex-device-cache-reload.json
The default bounded case records, independently for CPU and GPU:
a fresh process with an empty compilation cache;
a second fresh process reloading exactly that cache;
an in-process warm repeat in each process;
one implicit MHD-energy gradient;
actual state/value/gradient placement;
peak host RSS and device memory.
Each device gets a separate temporary cache, which is removed after the
measurements. --cache-dir PATH preserves entries for inspection but accepts
only an empty directory; the benchmark never deletes or mixes with a user’s
normal VMEX cache. Use --full for the larger parity case and --devices gpu
for the shortest WSL2 report. Compare forward_cache_reload_speedup and
gradient_cache_reload_speedup with the two reload_*_warm_speedup values:
the former isolates persistent-cache value across processes, while the latter
shows tracing/cache-load/dispatch overhead still paid above a true warm call.
The committed M4 CPU control is
benchmarks/device_cache_reload_m4.json; retain the generated JSON from a
WSL2 GPU run as the hardware-specific comparison artifact.