Choosing a backend#
This page says which backends solprop has, which one a run uses, how closely they agree
and how a run’s memory is budgeted. The physics of a run is decided before a backend
sees it: solprop first translates a simulation into one numerical problem — the arrays
and tables of its linear operator, its nonlinear responses and its transforms — and
every backend executes that same problem. The backends differ in speed, in where they
run and in the representations they support. Choose a backend that supports the full
configuration; a faster backend does not select a different physical model. Use the
native backend, which "auto" selects; mlx and numpy_ref are for the cases below.
backend |
what it is |
use it for |
|---|---|---|
|
the native backend, the solprop-native distribution built from the repository’s |
the one to use: what |
|
the Apple-silicon GPU through Apple’s MLX, in single precision only (single precision); the |
|
|
plain NumPy, the numerical reference: every other backend is tested against it. It is unthreaded, and roughly 6 to 40 times slower than threaded native |
checking a result, and the features native does not run. Also problems a GPU build refuses or runs slowly, in a process that has only that build |
Which backend a run uses#
backend="auto", the default of every entry point, and an explicit name select as follows.
The right-hand columns say what happens on this machine; where native isn’t consulted they
repeat the first:
request |
native usable |
processor below the floor, or its features unreadable |
native fails to load |
no native package |
|---|---|---|---|---|
double precision, |
|
refuses, naming the missing features or the failed check |
the load’s own error, unchanged |
refuses, saying how to install it |
|
|
as above |
as above |
as above |
TDSE response, |
|
the same |
the same |
the same |
single precision, |
|
the same |
the same |
the same |
|
runs; native isn’t consulted |
the same |
the same |
the same |
|
runs where MLX is installed; native isn’t consulted |
the same |
the same |
the same |
"auto" has no fallback. These cases stay distinct, so an import failure is never reported
as an unsupported platform, and a problem the chosen backend refuses is refused, naming what
was refused and what runs it, for you to choose explicitly. Which platforms and processors
have a native build is on the installation page. The
run records the backend it used, and the SOLPROP_REQUIRE_DEVICE setting makes a GPU job
stop rather than run anywhere else (a job that must run on a
device).
A GPU build of the native backend also refuses
a problem whose linear operator or nonlinear prefactor varies along z in a way
that could not be expanded in the medium’s parameters and had to be tabulated along z
instead: the adiabatic opt-out taper_coupling=False of a multimode capillary taper
whose expansion does not resolve (a kinked radius profile, for instance), or a gas
mixture of three or more species on independent density paths. The refusal leads with
what runs it: a CPU build of the native backend, in a second environment, or
backend="numpy_ref".
Multistep() (the Adams integrator) and RungeKutta(), each
the default of some interfaces
(the defaults), are available across
the binary64 backends for their supported configurations; single precision
refuses Multistep() on every backend. Beside a coupled flow, Adams requires its
exponential formulation; CF4 requires Runge–Kutta on the reference backend. Check the
response and geometry chapters for further restrictions. The backend used is
logged at INFO and recorded in the output.
from solprop.backends.registry import available, resolve_name
print("installed:", available())
print("auto resolves to:", resolve_name("auto"))
How close they are. Backend qualification compares short calculations with the NumPy reference using stated rounding and discretisation budgets. A different build, transform plan or device can change floating-point summation order, so cross-backend agreement need not be bitwise. Nor is a repeat on the same build: solprop promises numerical accuracy, not identical bits, across backends, builds, machines, thread counts, batches and repeated runs (SEP 25), and a rounding difference can change an adaptive run’s steps. Small differences can also grow in noise-sensitive propagation: compare the physical observable and numerical convergence, not only an array digest. Record the backend, build and thread settings with a result. See numerical qualification and its backend-equivalence ladder for the scope and measurements of the existing checks.
Speed and GPUs. How to install the native backend, build it for a GPU and run on a device is on The native backend; thread counts, OpenMP and BLAS settings, and what to use for scans are on Performance tuning.
Features that run on the reference backend#
numpy_ref is the numerical reference, and it is also the supported way to run these
features, which native does not run yet. Each is a limit of native, and each stays
supported on the supported platforms:
feature |
reached by |
tracked by |
|---|---|---|
the TDSE response |
|
|
|
by name |
|
single precision away from Apple silicon |
by name |
The TDSE row’s auto is that of the entry points; SimulationSettings built by hand with
tdse_execution names the backend. Running numpy_ref by
name on an unsupported platform may keep working, without a support promise. The list is not
a general waiver: a new feature still lands on both numpy_ref and native
(SEP 0), and joins the list only as an accepted
exception.
The removed numba_cpu backend#
solprop once had a third double-precision backend, numba_cpu, and 0.1.0 is the last
release with it. Naming it now raises BackendError, naming
native and numpy_ref, so a script or recipe that selects it needs backend="native", or
no backend= at all. Output, snapshots and a scan’s execution metadata that record
numba_cpu still load, and keep saying so: the record is not rewritten.
The backend is an execution resource, so changing it does not change a run’s identity. A
run can continue on native or numpy_ref, with its script’s or recipe’s backend changed
deliberately, through the normal resume checks; the original
execution record stays and the new one is added. Adams history crosses to the new backend,
except a frozen-residual coupled flow’s: a host engine’s history continues on numpy_ref,
and on native starts afresh and says why. Anything refused is recovered with
solprop==0.1.0.
Its settings are gone too: SOLPROP_NUMBA_THREADS, SOLPROP_NUMBA_REGIME,
SOLPROP_FFTW_THREADS, SOLPROP_TRANSVERSE_FFTW_THREADS, SOLPROP_PASS_TIMERS,
NUMBA_DISABLE_JIT and NUMBA_NUM_THREADS no longer do anything.
Which backend to use#
Use the native backend. numpy_ref is unthreaded and roughly 6 to 40 times slower than
threaded native, so it is for checking a result and for what only it runs; the
performance history records the native backend’s own timings.
The measurements behind the advice below are in the
indicative results; time your own problem for
ten steps before committing a long campaign to one backend.
Which native build. Build the native backend on the machine that runs it, or install a distribution build, the wheel (build profiles): the local build was the fastest backend on every coherent-wall and general modal case measured, at one, four and eighteen threads, and the distribution build ran within about 5 % of it. On Linux, prefer MKL (with oneAPI sourced) for TE/TM coherent walls, where it was 1.4–1.8 times faster than OpenBLAS; elsewhere the two tied.
Independent scans on a many-core CPU. Many one-thread workers beat one many-thread run (Performance tuning). Stay on the native backend for them. For a large campaign time one point of the scan with your real number of workers.
The general modal route (
from_waveguide(..., provider=...)): the compiled backends measured,nativeand the since-retirednumba_cpu, were 1.3 to 7.7 times faster thannumpy_refover ten steps at one thread, and a local native build gains a further factor at more threads.numpy_refis slowest on the analytic cubic, by five to eight times. Budget memory (execution resources) before raising the channel count, the node count or the grid.The coupled flow (a coherent wall, a varying wall, a split block): both CPU backends run the constant-Schur and frozen-residual sections;
CoherentWall(method="cf4")runs onnumpy_refalone. A single small wall run gains nothing past a few threads. On a varying wall the flow’s actions are re-formed every adaptive step, in a few per cent of a step on the native backend.On a GPU (a CUDA or HIP build of the native backend): pick it for large grids. Every general-route, coupled-flow and moving-guide case measured ran faster on either a data-centre NVIDIA GPU or an AMD Instinct GPU than on an 18-core CPU socket (about four and three times on a scaled graded-index guide); a small case is limited by kernel-launch latency (The native backend).
Batches. A fixed-schedule batch fuses the accepted updates of its members, while the response and transform work stays per member, so it does not amortise a device’s launch sequence; measure a batch’s gain for your own shape. On a CPU, a
Poolof one-thread workers is the better scan pattern.
Memory and scans#
For memory, output storage and scan workers, continue to execution resources. Choose resolution for the physics before tuning thread counts; reducing modes or nodes to fit memory changes the model or its numerical accuracy.
The Apple-GPU backend#
pip install solprop[mlx] installs MLX on macOS on
Apple silicon, and with it the mlx backend
(SEP 33). Apple GPUs have no binary64
arithmetic, so it runs precision="single" only, under the contract and with the
accuracy record of single precision; it is validated against the
reference backend’s single-precision path. It runs the capillary and fibre routes
(mode-averaged and multimode, field and envelope grids, Kerr, Raman, field-grid plasma,
both absorber schemes, Runge–Kutta and the low-storage march, lockstep batches) and
radial and Cartesian free space, including a line, with the safety ramp, and the
envelope-grid plasma. It reads the transverse edge on the host, from its state widened
to binary64 (transverse boundaries).
The backend is experimental in this release. Its Metal kernels run in the macOS
default lane of continuous integration, which requires a usable Metal device, but
coverage and runtime type checks remain absent there
(the remaining CI gaps). Its speed has not been
re-measured on the default grids, whose 2-3-5-smooth sizes MLX’s GPU transform runs
four to eight times slower than powers of two: build single-precision grids with
fft_size="pow2" (grid sizes on mlx).
MLX computes float32 matrix products in TF32 by default on Apple GPUs, which SEP 25’s
arithmetic rules forbid. solprop sets MLX_ENABLE_TF32=0 when it imports the backend,
but MLX reads the setting once, when it first initialises: in a process that used MLX
before, set it in the shell (export MLX_ENABLE_TF32=0) or before any import mlx.
The backend measures its matrix products when it is built and refuses to run if they
are TF32.