The native backend: fast CPUs and GPUs#
solprop’s physics is decided before a backend sees it: every backend executes the same
translated problem, and the NumPy reference backend is the oracle that the others are
tested against (see Choosing a backend). The native backend is the
fast one. It is the package solprop-native, built from the native/ directory of the
solprop repository at solprop’s own version: C++ kernels written with
Kokkos, bound to Python with nanobind, and compiled for the machine
that runs them —
on a CPU with OpenMP threads, FFTW and a vendor BLAS (Accelerate, MKL, OpenBLAS or AOCL). A locally built CPU backend is the fastest backend measured on the coherent-wall and general modal cases, and its lead over the in-tree
numba_cpubackend grows with the thread count: on a large graded-index guide it takes about 0.6 ofnumba_cpu’s time at one thread and about a quarter at eighteen;on an NVIDIA GPU with CUDA (cuFFT and cuBLAS), or an AMD GPU with HIP (rocFFT and rocBLAS).
You never need it for correctness: without it solprop runs everything on numpy_ref and
numba_cpu. With it installed, backend="auto" — the default of every entry point —
selects it.
What it runs#
The native backend runs:
field and envelope grids; single-mode, multimode and polarisation-resolved modal runs;
Kerr and third-harmonic terms, field and envelope plasma, scalar and vector Raman responses, gas mixtures with per-species channels, noise and absorbing boundaries;
the general modal route (guides that supply their own vector E and H, including guides that vary along z), the coupled flow of the coherent capillary wall and of tapers, with its
constant_schurandfrozen_residualsections;radial, Cartesian, line and planar free-space and planar-guide geometries;
fixed-schedule batches (lockstep scan members);
the Chebyshev z table: a linear operator or nonlinear prefactor that varies along z in a way that has to be tabulated, rather than expanded in the medium’s parameters. Examples are a
SpecifiedModewhose dispersion moves along z, the adiabatic opt-outtaper_coupling=Falseof a multimode capillary taper whose expansion does not resolve, and a gas mixture of three or more species on independent density paths. The CPU build holds the table’s coefficients and evaluates it at each step’s positions in its own kernels (z tables).
Everything numba_cpu runs, the native CPU build runs too
(SEP 29, §6.6). The GPU
builds do not run the z table: they refuse it before anything is allocated, giving the
table’s size, since it would be held resident on the device; run such a problem on a
CPU build, or with backend="numba_cpu" in a process that has the GPU build installed.
Adams snapshots and resume history run on the native backend as on the others. It
keeps the history of the default Multistep() integrator resident in its engine and
copies the history’s rows to the host only where a snapshot is kept: at each save plane
of an HDF5 output that keeps the resume history, and for each periodic snapshot. A
resume installs them again and continues the history (resuming).
A frozen-residual coupled flow’s history is kept in a native form (each sample’s
generator applied to its state), so a snapshot of it continues only on the native
backend, and a host engine’s snapshot of it only on numpy_ref or numba_cpu; a
mismatch starts the history afresh and says why. Other histories cross between backends.
Separately, the cf4 section method of a coupled flow (CoherentWall(method="cf4"))
runs on numpy_ref only, with the Runge–Kutta march; neither compiled backend runs it.
The planar guide resolved in y is built into the GPU builds but has not been qualified
on a device. solprop never falls back silently: an import failure, an incompatible build
or a refused problem raises, naming the backend to use instead. The one fallback is
announced: on a processor below the installed build’s CPU baseline, backend="auto"
uses numba_cpu with a warning (choosing a backend).
Installing#
The native backend is solprop’s native extra, which requires solprop-native at
solprop’s own version:
pip install "solprop[native]"
Precompiled wheels serve macOS on Apple silicon (osx-arm64), Linux on x86-64 (linux-64), Linux on aarch64 (linux-aarch64) and Windows on x86-64 (win-64). They are the distribution build: compiled for a CPU baseline (x86-64-v3, any Apple silicon Mac, Neoverse N1; processor requirements) and carrying a BLAS that shares the backend’s threads. Elsewhere, and for an NVIDIA or AMD GPU, build the package from source. A source build can also be the local profile, compiled for the machine that runs it with its own vendor BLAS, which is a few per cent faster than the wheel on the measured cases. A third profile, reference, is for development checks only and is noticeably slower. The profiles agree to rounding, not bit for bit. Building the native backend gives the requirements, the commands for Linux and macOS, every build option, and the CUDA and HIP builds.
Check the installation:
python -m solprop_native # the build record, one JSON object
python -c "import solprop_native as sn; sn.check_build()"
check_build() raises if the build is inconsistent (for example a GPU build that sees
no device) or does not fit the installed solprop: the two packages share a versioned
kernel contract, and check_build() names any solprop member the build relies on that
is missing. Install the two at the same version, from one release or one checkout. The build
record reports the profile (build_profile), the BLAS (blas_selection,
blas_library), FFTW, the compiler flags, the thread counts and, on a GPU build, the
device. Then, in Python:
import solprop
from solprop.backends.registry import available, resolve_name
print("installed backends:", available())
print("backend='auto' means:", resolve_name("auto"))
With logging on (solprop.configure_logging("INFO")) every run logs
Using native backend, and the backend and its settings are recorded in the output file
(run.metadata.backend).
Working on solprop and its native backend. In a development checkout, one command
builds the native package from native/ and installs it editable into the repository’s
environment beside solprop:
uv sync --extra native
A plain uv sync leaves it out, and removes a copy installed by hand. The editable
install does not rebuild itself: run uv sync --extra native --reinstall-package solprop-native after a native change. uv run poe check-native runs the native gate
and uv run poe check-all both gates. A build stays valid across solprop changes until
solprop’s kernel schema (SCHEMA_VERSION) changes or check_build() names a member the
installed solprop lacks. Running solprop’s test lanes against the build is described in
the developer notes on testing.
Running on a GPU#
GPU support is chosen when the package is built, and there are no GPU wheels: for an
NVIDIA GPU (CUDA) or an AMD GPU (HIP), on Linux, build the package from source with the
device enabled and the GPU’s architecture named, as
Building the native backend describes. One
installation is either a CPU build or a GPU build; to compare, install the two in
separate environments. check_build() refuses a GPU build that cannot see a device.
Once installed, nothing in your scripts changes: backend="auto" runs on the device.
The field stays resident on the device between steps and is copied back only at saved
planes. Things to know:
One device per process. Choose it with
CUDA_VISIBLE_DEVICESorHIP_VISIBLE_DEVICES. To use several GPUs, run independent problems (scan points, noise seeds) in separate processes, each seeing one device.Small problems belong on the CPU. A single-mode fibre on a few thousand time samples is limited by kernel-launch latency, not arithmetic, and usually runs faster on the CPU. In a process that has the GPU build installed, pass
backend="numba_cpu"for such problems. The GPU wins on large grids: multimode and general modal guides with many nodes, coherent walls with many channels, broadband supercontinuum grids, and free-space runs.On-axis plasma statistics. The four modal
on_axis_*plasma statistics require a host copy of the field at each requested save plane. Their CPU parity is qualified; they are not yet qualified on a GPU, and there is no device-only axis scan. See the statistics catalogue.Reproducibility. A device answer is not bit for bit the CPU build’s, because the device sums in its own order; the difference is at the level of rounding and is covered by the same derived error bounds that every backend is tested against. A repeat on one build is held to the same bounds, not to identical bits (SEP 25).
Time your own problem first. Run ten steps on the device and on the CPU before committing to either for a long campaign.
Threads on the CPU build#
The CPU build uses the process’s thread budget by default: the CPUs in its affinity
mask (which taskset or a batch scheduler’s allocation narrows), a container’s CPU
quota and SOLPROP_THREADS, solprop’s budget for every CPU backend
(performance tuning). SOLPROP_NATIVE_THREADS narrows it further, and
four further variables size its individual pools; all are read once, when
solprop_native is imported, so set them before starting Python:
SOLPROP_NATIVE_THREADS=8 OMP_PROC_BIND=spread OMP_PLACES=cores OMP_WAIT_POLICY=passive \
python my_run.py
Performance tuning explains every variable, how the defaults are chosen, the OpenMP and BLAS settings that interact with them, and what to use for one large run, for a scan of many runs and on a cluster.
When something goes wrong#
ImportError, a schema mismatch, orcheck_build()naming a missing member. The installed solprop-native does not fit the installed solprop; install both at the same version (pip install --force-reinstall "solprop[native]", or both from one checkout as in Building the native backend).“refuses this problem”. The message names what was refused and which backend runs it; pass that backend explicitly.
A
RuntimeErroron macOS aboutpyfftw, or a crash or hang. Two OpenMP runtimes were loaded, or would have been. Importsolprop_nativeat the top of the script, before anything importspyfftw(the package raises the error when it detects the wrong order); see Performance tuning.Slower than expected. Check
python -m solprop_native: areferenceprofile, no vendor BLAS (blas_backendnone) or a generic architecture (neithernative_arch_enablednor acpu_baseline) each cost a large factor. Then see Performance tuning.
The native backend’s place in this repository, its parity with numba_cpu and its
precompiled wheels were proposed and carried out by
SEP 29.