Building the native backend#
The native backend is the solprop-native package, built from the
native/ directory of the solprop repository at solprop’s own version. On the four
platforms with precompiled wheels — macOS on Apple silicon (osx-arm64), Linux on x86-64
(linux-64), Linux on aarch64 (linux-aarch64) and Windows on x86-64 (win-64) —
pip install "solprop[native]" installs it and nothing on this page is needed
(installation). Build it from source to:
run on a GPU: NVIDIA with CUDA or AMD with HIP. There are no GPU wheels;
get the local build, compiled for the machine that runs it with that machine’s vendor BLAS, which is a few per cent faster than the wheel on most cases and more on some;
run on a platform without a wheel, or work on the backend itself.
The instructions cover Linux and macOS. One build is either a CPU build or a GPU build; the arithmetic is double precision throughout, and no build enables fast-math.
Requirements#
requirement |
version and notes |
|---|---|
Python |
3.14, with NumPy 2.3 or newer, and solprop at the same version installed in the same environment |
CMake |
3.23 or newer (with build isolation, scikit-build-core fetches one from PyPI if none is found) |
build tool |
Ninja is recommended; scikit-build-core uses it when available |
C++20 compiler, macOS |
Clang 14 or newer; Homebrew LLVM is recommended and selected by default |
C++20 compiler, Linux CPU |
GCC 14 or newer, IntelLLVM ( |
C++20 compiler, CUDA |
GCC 14 or newer as the host compiler, with |
C++20 compiler, HIP |
ROCm’s Clang ( |
OpenMP (CPU builds) |
the compiler’s runtime; on macOS Homebrew’s |
FFT (CPU builds) |
FFTW 3 with its threaded libraries, in double and single precision (or MKL’s FFTW interface) |
BLAS (CPU builds) |
optional but recommended: Accelerate (macOS), OpenBLAS, MKL or another LP64 BLAS |
GPU libraries |
cuFFT and cuBLAS (CUDA), or rocFFT and rocBLAS (HIP) |
network |
configuring fetches the pinned dependencies below from GitHub (see offline builds) |
git |
the source must be a git clone of the repository: configuring records the commit, and the version comes from the repository’s tags |
CMake fetches and builds these at pinned versions; nothing needs to be installed for them:
dependency |
version |
use |
|---|---|---|
5.2.1 |
execution spaces, views and parallel patterns |
|
5.2.0 |
dense GEMM (BLAS component only), dispatching to the vendor BLAS |
|
2.0.0 |
FFTs over FFTW, cuFFT or rocFFT |
|
1.17.0 |
the native tests only, when |
The Python build requirements are scikit-build-core 0.11 and nanobind 2.9 or newer. CPU
builds contract multiply-adds (-ffp-contract=fast with GCC,
-ffp-contract=fast-honor-pragmas with Clang and icpx, after -fno-fast-math and
-fp-model=precise) except in a few protected blocks, as
SEP 25 sets out;
build_info().compiler_flags and optimization_policy record the setting.
A CPU build#
Clone the repository and install both packages from it into one environment.
/path/to/env/bin/python stands for the Python of that environment.
macOS (Apple silicon or Intel), with Homebrew:
brew install llvm libomp fftw cmake ninja pkg-config
git clone https://github.com/LupoLab/solprop
cd solprop
uv pip install --python /path/to/env/bin/python . ./native
native/pyproject.toml selects Homebrew’s Clang (/opt/homebrew/opt/llvm/bin/clang++
on Apple silicon, /usr/local/opt/llvm/bin/clang++ on Intel), and CMake finds Homebrew’s
keg-only FFTW and libomp by itself. The build links Apple’s Accelerate. If the Xcode
licence state blocks the Command Line Tools, select their SDK explicitly and use a fresh
build directory:
export DEVELOPER_DIR=/Library/Developer/CommandLineTools
export SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk
export CMAKE_ARGS="-DCMAKE_OSX_SYSROOT=$SDKROOT"
For an Apple silicon performance build, the pinned dependency recipe enables FFTW
3.3.11’s NEON codelets in both precisions. Compiling scalar code with -mcpu
does not enable those codelets. The recipe also retains the Arm cycle counter needed
by measured planning, the Apple M1 distribution baseline and the macOS 14 deployment
target. Its FFTW threading libraries and the native extension share one source-built
LLVM OpenMP runtime; Accelerate remains the BLAS provider.
From the repository root, use a fresh, owned dependency prefix:
native_deps="$PWD/native/build/neon-dependencies"
MACOSX_DEPLOYMENT_TARGET=14.0 native/ci/build-deps.sh "$native_deps"
export CMAKE_ARGS="-DFFTW_ROOT=$native_deps -DSOLPROP_NATIVE_LIBOMP_LIBRARY=$native_deps/lib/libomp.dylib -DSOLPROP_NATIVE_LIBOMP_INCLUDE_DIR=$native_deps/include -DSOLPROP_NATIVE_BUNDLED_LICENCES=$native_deps/licences"
uv sync --extra native --reinstall-package solprop-native
uv run --no-sync python -m solprop_native
Check the reported fft_library and openmp_library before benchmarking. The
pinned upstream configuration
supports --enable-neon for double and single precision on AArch64. The installed
libraries identify their codelet family in fftw_version and fftwf_version, which
both end in -neon for this recipe. Keep these prefixes separate from a comparison’s
baseline libraries. A pyFFTW build used in the same process must also share this
runtime. To build the existing pyFFTW dependency against that same owned prefix:
pyfftw_version=$(uv run --no-sync python -c 'from importlib.metadata import version; print(version("pyfftw"))')
CC=/opt/homebrew/opt/llvm/bin/clang \
CXX=/opt/homebrew/opt/llvm/bin/clang++ \
PYFFTW_FFTW_PREFIX="$native_deps" \
LDFLAGS="-L$native_deps/lib -Wl,-rpath,$native_deps/lib" \
uv pip install --python .venv/bin/python --no-deps --no-binary pyfftw \
--reinstall --no-cache "pyfftw==$pyfftw_version"
The uncached source build prevents reuse of a wheel linked to another runtime. Keep the project’s locked pyFFTW version when qualifying a comparison. See OpenMP runtimes below for import ordering and the limits of mixing independently bundled wheels.
For a distributable wheel, native/ci/build-wheel-macos.sh OUT_DIR DEPS_PREFIX
builds the distribution profile, repairs its library references with delocate, and
includes the FFTW and OpenMP licences. Use a fresh dependency prefix when changing
the recipe: that helper intentionally reuses an existing prefix. Validate the repaired
wheel in a clean environment with python -I -m solprop_native, its linked libraries,
the native tests and the import-order/coexistence checks before distributing it.
Linux, on Debian or Ubuntu. Ubuntu 24.04’s default compiler is GCC 13, which the build refuses; install GCC 14 from the distribution’s archive:
sudo apt install g++-14 cmake ninja-build pkg-config libfftw3-dev libopenblas-dev
git clone https://github.com/LupoLab/solprop
cd solprop
CXX=g++-14 uv pip install --python /path/to/env/bin/python . ./native
libfftw3-dev provides the OpenMP and pthreads FFTW libraries in both precisions. For
MKL instead of OpenBLAS, source oneAPI first (source /opt/intel/oneapi/setvars.sh): a
set MKLROOT makes the automatic search choose MKL. Other compilers and libraries are
in Linux compilers and libraries.
python -m pip install . ./native works the same way in an activated environment.
Compilation takes several minutes; uv and pip hide the build output, and -v shows
it, including the configure summary line described below.
A development build#
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, with any build options in CMAKE_ARGS
(below); a device build is made the same way. 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 in the developer notes on
testing, and the native package’s own tests in
native/README.md.
Checking a build#
python -m solprop_native | python -m json.tool
python -c "import solprop_native as sn; sn.check_build(); print(sn.build_info().build_profile)"
python -m solprop_native prints the build record, build_info(), as one sorted JSON
object suitable for a run log:
provenance:
package_version,git_commit(with+dirtyfor a modified tree),schema_version(the kernel-contract version the build implements),solprop_version_built_against;dependencies:
kokkos_version,kokkos_kernels_version,kokkos_fft_version,nanobind_version;profile and BLAS:
build_profile,cpu_baseline,gemm_provider,tpl_blas_enabled,blas_backend,blas_selection,blas_thread_policy,blas_threads,blas_threading,blas_library;execution:
execution_space,host_execution_space,device_backend,device_arch,device_count,openmp_enabled,openmp_library;FFT:
fft_backend,fft_planner,fft_normalization,fft_library,raman_fft_option,raman_fft_library;threads:
requested_threads,runtime_threads,thread_count,fftw_threads,raman_fft_threads,raman_tail_fft_threads,threaded_fft_min_samples(Performance tuning);compilation:
compiler,compiler_family,compiler_flags,cxx_standard,build_type,native_arch_enabled,aggressive_vectorization_enabled,ipo_enabled,fast_math_enabled,optimization_policy,precision,layout;host:
python_version,numpy_version,platform_machine.
The library paths (fft_library, blas_library, openmp_library) are the files
actually loaded, the reliable way to confirm which FFT and BLAS a process uses.
raman_fft_option identifies the compiled Raman wrapper, which can select FFTW for
individual plans even when its value is ACCELERATE. The backend’s execution
metadata records the selected full-chunk raman_fft_provider and full/tail task
counts; selection also accounts for the actual tail width.
native_arch_enabled reports the package’s option only; architecture flags passed by
hand through CXXFLAGS appear in compiler_flags.
check_build(problem=None) raises solprop.exceptions.BackendError when the installed
solprop lacks a driver member or bundle field the build relies on (it names them);
when problem declares a different kernel-contract schema; when the build enables
fast-math; when the execution-space record is inconsistent; when a GPU build sees no
device; when the GEMM provider is unknown or its BLAS is missing or unidentified; when
a reference build carries a vendor BLAS or machine-specific code, or a distribution
build carries native code or no BLAS; and when a GPU build’s BLAS is not the device’s.
Then, in solprop:
import solprop
from solprop.backends.registry import resolve_name
print(resolve_name("auto")) # "native" when solprop_native is installed
solprop.configure_logging("INFO") # each run logs "Using native backend"
resolve_name only probes whether the package can be found; the log line reports the
backend actually constructed.
Build profiles#
A plain build is the local profile, the fast build for the machine it is built on:
a vendor BLAS behind Kokkos Kernels, found automatically: Accelerate on macOS; on Linux MKL when
MKLROOT(orSOLPROP_NATIVE_MKL_ROOT) names it and its LP64 libraries are found, otherwise OpenBLAS; cuBLAS or rocBLAS in a GPU build;the
HYBRIDdense-transform policy, which sends the eligible multimode transforms to the vendor GEMM;code for the build machine’s own architecture (
Kokkos_ARCH_NATIVE, for example-march=native) in CPU builds, and checked interprocedural optimisation (IPO/LTO) in Release builds.
If the search finds no BLAS, a local build still succeeds with a configure warning and runs the native-order GEMM. A vendor BLAS matters most for the dense modal transforms of multimode problems; single-mode and free-space problems depend on it much less.
The distribution profile, SOLPROP_NATIVE_PROFILE=DISTRIBUTION, is the build the
wheels are made with, for machines other than the build machine. It is a CPU profile:
a CPU baseline instead of the build machine’s architecture:
-march=x86-64-v3(AVX2, FMA, BMI1/2: Intel Haswell, AMD Excavator and newer) on x86-64,-mcpu=apple-m1on Apple silicon,-mcpu=neoverse-n1on Linux aarch64. For an older x86-64 processor (no AVX2), build withSOLPROP_NATIVE_CPU_BASELINE=x86-64-v2(SSE4.2 and POPCNT): on the benchmark core it costs up to 2 to 4 %, and on the general modal route about a quarter at one thread and 7 to 12 % at full width, againstx86-64-v3. The bundled FFTW and OpenBLAS choose their kernels at run time either way. The package records its baseline and refuses to import on a processor below it, naming the missing features (from/proc/cpuinfoon Linux, fromIsProcessorFeaturePresenton Windows, where AVX2 stands for the other x86-64-v3 features Windows does not report); solprop’sbackend="auto"then usesnumba_cpuwith a warning;a BLAS that every such machine has and that shares the extension’s threads: Accelerate on macOS; on Linux an OpenBLAS 0.3.34 that the configure step builds with OpenMP and the extension’s own compiler (
native/cmake/OpenMPOpenBLAS.cmake), so that it runs on the extension’s OpenMP runtime, selects its kernels at run time and carriessolprop_-prefixed symbols that never collide with NumPy’s copy. It is built once per build tree;SOLPROP_NATIVE_OPENBLAS_ROOTnames a prefix that already holds one. The Windows wheel links the same OpenBLAS release built static with OpenMP on LLVM’slibomp, the runtime of its Kokkos and FFTW, named bySOLPROP_NATIVE_OPENBLAS_ROOTwithSOLPROP_NATIVE_DISTRIBUTION_BLAS=OPENMP_OPENBLAS; a Windows distribution build without them links thescipy-openblas32OpenBLAS through itsscipy_symbols. The BLAS’s licence is copied into the package.
cd native
uv build --wheel -Ccmake.define.SOLPROP_NATIVE_PROFILE=DISTRIBUTION \
-Cbuild-dir="build/distribution-{wheel_tag}"
On the measured capillary and general-route cases a Linux distribution build ran within
about 5 % of a local build. The reference profile, SOLPROP_NATIVE_PROFILE=REFERENCE,
links no vendor BLAS and generates no machine-specific code. It is the gate’s
sanitizer and clang-tidy build, slower than the other two, and holds no privileged
numerical answers.
The three profiles agree with solprop’s reference backend within the same derived bounds; they differ from each other in the last bits, because a vendor BLAS sums in its own order. Keep each profile in its own build directory; switching the profile in one tree rebuilds everything. Configuring prints one summary line, for example
-- solprop-native build profile: local; BLAS Accelerate (automatic: Accelerate (macOS)); GEMM provider HYBRID; native architecture ON; CPU baseline none; IPO ON (Release builds)
and build_info() reports the same choice in build_profile, cpu_baseline,
blas_backend, blas_selection, blas_library, native_arch_enabled and
ipo_enabled.
Passing CMake options#
Pass options through the CMAKE_ARGS environment variable or scikit-build-core’s -C
settings; both work with pip, uv pip and uv build:
CMAKE_ARGS="-DSOLPROP_NATIVE_BLAS_VENDOR=OPENBLAS" uv pip install --python /path/to/env/bin/python ./native
uv pip install --python /path/to/env/bin/python ./native -Ccmake.define.SOLPROP_NATIVE_ENABLE_TPL_BLAS=OFF
In the repository’s own environment, pass them to uv sync and force the rebuild:
CMAKE_ARGS="-DSOLPROP_NATIVE_BLAS_VENDOR=OPENBLAS" \
uv sync --extra native --reinstall-package solprop-native
Select the compiler with CXX or -DCMAKE_CXX_COMPILER=.... CMake caches compilers and
library paths, so use a fresh build directory (-Cbuild-dir=...) whenever you change
the compiler, the profile or a library provider. The default build directory is
native/build/{wheel_tag}, and the build type is Release.
Option reference#
The options are read by native/CMakeLists.txt and native/cmake/HostLibraries.cmake.
Profile and optimisation
option |
default |
values and effect |
|---|---|---|
|
|
|
|
|
|
|
|
the oldest processor a non-native CPU build runs on: |
|
|
checked IPO/LTO for CPU Release builds; never for CUDA or HIP |
|
|
sets |
|
|
a Clang CPU build with source coverage, for development only; disables IPO, and its timings are not performance evidence |
Execution space
option |
default |
values and effect |
|---|---|---|
|
|
the OpenMP host execution space and threaded FFTW. |
|
|
the Kokkos Threads (C++ threads) host space with FFTW’s threads library, for a build that adds no OpenMP runtime (a fallback for Windows builds); requires |
|
|
the CUDA execution space with cuFFT (Linux, GNU host compiler) |
|
|
the HIP execution space with rocFFT (Linux, ROCm Clang); exclusive with CUDA |
|
|
the Kokkos architecture suffix, |
|
|
the Kokkos architecture suffix, |
|
|
the maximum GPU response and scan team width: 64, 128, 256, 512 or 1024 (the |
|
|
the GPU deterministic-reduction block width: 64, 128, 256, 512 or 1024 |
|
|
how the field plasma column’s band rule reads its halo: |
BLAS and dense transforms
option |
default |
values and effect |
|---|---|---|
|
|
|
|
|
the distribution build’s BLAS on Linux and Windows: |
|
the environment variable of that name |
the prefix of a prebuilt OpenMP OpenBLAS for the distribution build: on Linux |
|
|
|
|
empty |
a prefix searched first for the BLAS |
|
empty |
an explicit |
|
|
where the LP64 MKL libraries are looked for |
|
|
|
FFT
option |
default |
values and effect |
|---|---|---|
|
|
|
|
|
|
|
|
construction-time Raman selection: with NEON FFTW, vDSP handles short parallel power-of-two rows (up to 16,384 samples); serial and longer rows use Kokkos-FFT. Without NEON, only short serial rows use Kokkos-FFT. |
|
the Homebrew prefix on macOS |
a non-default FFTW installation |
Development and diagnostics
option |
default |
values and effect |
|---|---|---|
|
|
builds the GoogleTest native tests and registers them with CTest |
|
|
|
|
|
runs clang-tidy while compiling the package’s sources |
|
|
AddressSanitizer and UBSan on the package’s targets; CPU builds only |
|
|
builds the |
|
the environment variable of that name |
a directory of |
The installed module of a local build keeps the directories of a BLAS it linked outside the system library path in its runpath, so an MKL build loads without sourcing oneAPI at run time; a Linux distribution build’s runpath is the package directory, where its OpenBLAS is.
Presets#
native/CMakePresets.json defines development builds, all with BUILD_TESTING=ON:
cpu-debug, cpu-tidy and cpu-sanitize (reference profile, Debug), cpu-benchmark
(local, Release, IPO and benchmarks), the CUDA builds cuda (hybrid GEMM with cuBLAS),
cuda-tpl, cuda-native and cuda-custom (the other GEMM providers, for comparison),
and hip. The presets do not choose a compiler: pass -DCMAKE_CXX_COMPILER=... (on
macOS, Homebrew’s clang++) and -DPython_EXECUTABLE=....
Offline builds#
The pinned dependencies are fetched with CMake’s FetchContent. To build without network
access, point it at source trees of the same versions:
-DFETCHCONTENT_SOURCE_DIR_KOKKOS=/path/to/kokkos-5.2.1
-DFETCHCONTENT_SOURCE_DIR_KOKKOSKERNELS=/path/to/kokkos-kernels-5.2.0
-DFETCHCONTENT_SOURCE_DIR_KOKKOSFFT=/path/to/kokkos-fft-2.0.0
-DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=/path/to/googletest-1.17.0 # only with BUILD_TESTING
Linux compilers and libraries#
Any of these compilers builds the CPU backend; Kokkos, Kokkos Kernels and Kokkos-FFT stay pinned whichever is used.
compiler |
notes |
|---|---|
GCC 14 or newer |
the reference route, with GNU OpenMP ( |
IntelLLVM ( |
compiled with |
Clang 14 or newer, AMD AOCC |
compiled with |
provider |
how to select it |
|---|---|
OpenBLAS |
the automatic choice when MKL is not configured; |
MKL BLAS |
|
AOCL-BLIS and other BLAS |
|
FFTW |
the default; |
MKL FFT |
|
FFTW threading. OPENMP links FFTW’s OpenMP library, which must use the same OpenMP
runtime as the rest of the build: GNU libgomp for distribution packages built with
GCC. PTHREADS links FFTW’s pthreads library instead; it is required for IntelLLVM and
is the safer choice whenever a second OpenMP runtime could otherwise be loaded. With
pthreads FFTW, start with OMP_PROC_BIND=false: a bound OpenMP main thread can confine
the FFTW worker threads it creates to its own core.
OpenBLAS threading. A pthreads OpenBLAS (the Debian and Ubuntu default) keeps its own
thread pool beside the backend’s OpenMP pool and, at the full core count, oversubscribes
the cores: the modal and wall cases ran 2 to 75 times slower than with one BLAS thread.
At start-up the package therefore holds a pthreads OpenBLAS to one thread and caps an
OpenMP OpenBLAS, which shares the runtime, at the backend’s thread count, unless
OPENBLAS_NUM_THREADS, GOTO_NUM_THREADS or OMP_NUM_THREADS is set;
build_info().blas_thread_policy records which. An OpenMP OpenBLAS on the same runtime
(the Linux distribution build, or Ubuntu’s libopenblas0-openmp) is the one to prefer
for a local Linux build. build_info() reports the loaded library’s blas_threading
(pthreads, OpenMP or serial) and blas_threads.
A compiler and library combination that configures is not thereby validated: run the
native and Python tests after changing either (native/README.md).
GPU builds#
There are no GPU wheels: an NVIDIA or AMD GPU needs a build from source, on Linux. A GPU
build replaces the OpenMP host space with Serial, FFTW with the device FFT and the host
BLAS with the device BLAS. SOLPROP_NATIVE_ENABLE_OPENMP=OFF is required, the host
BLAS and FFT options do not apply, and the host architecture and IPO are off. The local
profile links the device BLAS; the reference profile does not; the distribution profile
is CPU-only.
CUDA#
Requirements: CUDA 12.8 or newer with cuFFT and cuBLAS, and GCC 14 or newer as the C++
compiler. GCC 14 is the floor of every Linux build; CUDA 12.8 is the first toolkit that
accepts it as the host compiler (12.6 stops at GCC 13.2), and CMake refuses either below
its floor. The device build is qualified on CUDA 13 with GCC 14; CUDA 13 needs an NVIDIA
driver of the 580 series or newer and no longer targets Maxwell, Pascal or Volta, so a
V100 builds with CUDA 12.8 or 12.9. Kokkos compiles the device code through its nvcc_wrapper, which uses
NVCC_WRAPPER_DEFAULT_COMPILER as the host compiler. CMake finds nvcc from
CUDA_ROOT or CUDAToolkit_ROOT when given, otherwise on PATH or under
/usr/local/cuda or /opt/cuda.
SOLPROP_NATIVE_CUDA_ARCH names the GPU generation as a Kokkos architecture suffix:
VOLTA70, TURING75, AMPERE80, AMPERE86, ADA89, HOPPER90 (the default),
BLACKWELL100 or BLACKWELL120, among others in the Kokkos documentation.
git clone https://github.com/LupoLab/solprop
cd solprop
export CUDA_ROOT=/usr/local/cuda
export CXX=g++-14 NVCC_WRAPPER_DEFAULT_COMPILER=g++-14
export CMAKE_ARGS="-DSOLPROP_NATIVE_ENABLE_CUDA=ON -DSOLPROP_NATIVE_ENABLE_OPENMP=OFF \
-DSOLPROP_NATIVE_CUDA_ARCH=AMPERE80"
uv pip install --python /path/to/env/bin/python . ./native
The cuda preset configures the same build for development, with the native tests and
the GEMM benchmark (cmake --preset cuda -DPython_EXECUTABLE=... -DCUDA_ROOT=..., from
native/).
HIP#
Requirements: ROCm with its Clang (hipcc or amdclang++), and the HIP runtime, rocFFT
and rocBLAS development packages (headers and CMake configuration files; the package
names vary between ROCm releases and distributions, for example hip-dev, rocfft-dev
and rocblas-dev). Point CMake at ROCm with CMAKE_PREFIX_PATH.
SOLPROP_NATIVE_HIP_ARCH names the GPU as a Kokkos architecture suffix, for example
AMD_GFX90A, AMD_GFX942 (the default), AMD_GFX950, AMD_GFX1100 or AMD_GFX1201.
git clone https://github.com/LupoLab/solprop
cd solprop
export ROCM_PATH=/opt/rocm
export PATH="$ROCM_PATH/bin:$PATH"
export CMAKE_ARGS="-DCMAKE_CXX_COMPILER=$ROCM_PATH/bin/hipcc -DCMAKE_PREFIX_PATH=$ROCM_PATH \
-DSOLPROP_NATIVE_ENABLE_HIP=ON -DSOLPROP_NATIVE_ENABLE_OPENMP=OFF \
-DSOLPROP_NATIVE_HIP_ARCH=AMD_GFX942"
uv pip install --python /path/to/env/bin/python . ./native
If the ROCm installation lacks clang-scan-deps, add -DCMAKE_CXX_SCAN_FOR_MODULES=OFF
(the package uses no C++ modules). The hip preset configures the same build for
development.
After a GPU build#
check_build() refuses a GPU build that sees no device, and build_info() reports
device_backend, device_arch, device_count and the loaded cuFFT/rocFFT and
cuBLAS/rocBLAS libraries. Running on the device — one device per process, which problems
belong on the CPU, reproducibility — is in
the native backend. Use the vendors’ tools (Compute
Sanitizer, rocprof) to check device memory; the CMake sanitizer option is for CPU
builds. GPU builds are qualified with CUDA 13 and a ROCm 7 stack; other toolkit versions
are expected to work but are untested.
OpenMP runtimes and pyFFTW#
A process can safely load only one OpenMP runtime. Two different runtimes (LLVM’s
libomp and GNU’s libgomp, or two copies of one) each start their own thread pools,
oversubscribe the cores, and can crash when the second initialises. A CPU build
therefore links every native part — Kokkos, FFTW’s threaded library, an OpenMP BLAS —
against one runtime: on macOS, either Homebrew’s libomp with its FFTW, or the one
runtime in the owned dependency prefix built above.
solprop’s numba_cpu backend uses pyFFTW, whose
published wheels bundle their own OpenMP runtimes, so running both backends in one
process loads a second runtime.
On macOS, import
solprop_nativebefore anything imports pyFFTW. The package raisesRuntimeErrorat import if pyFFTW is already loaded.Import order alone does not remove pyFFTW’s bundled runtimes. When one process must run both the native and the Numba backends, build pyFFTW from source against the same FFTW, in the same environment.
pyFFTW then shares FFTW’s process-wide planner with Kokkos-FFT. The package finalises the Kokkos runtime it owns at the very end of interpreter shutdown, after Python has released pyFFTW’s plans.
macOS, against Homebrew’s FFTW and libomp:
CC=/opt/homebrew/opt/llvm/bin/clang \
CXX=/opt/homebrew/opt/llvm/bin/clang++ \
PYFFTW_FFTW_PREFIX=/opt/homebrew/opt/fftw \
LDFLAGS="-L/opt/homebrew/opt/libomp/lib -Wl,-rpath,/opt/homebrew/opt/libomp/lib" \
uv pip install --python /path/to/env/bin/python \
--no-deps --no-binary pyfftw --reinstall pyfftw
Linux, against the system FFTW with pthreads, so that pyFFTW adds no OpenMP runtime:
CC=gcc CXX=g++ PYFFTW_USE_PTHREADS=1 PYFFTW_FFTW_PREFIX=/usr \
uv pip install --python /path/to/env/bin/python \
--no-deps --no-binary pyfftw --reinstall pyfftw
For an FFTW installed elsewhere, set PYFFTW_FFTW_PREFIX to its prefix; pyFFTW needs the
single-precision FFTW libraries as well as the double-precision ones. On macOS also
change both library paths in LDFLAGS to that prefix’s OpenMP library directory. With AOCL’s FFTW,
build pyFFTW with AOCC and without PYFFTW_USE_PTHREADS, so that it uses AOCL’s OpenMP
interface; do not combine a standard FFTW pthreads library with AOCL’s FFTW core.
Wheels and redistribution#
A wheel meant for other machines is built with SOLPROP_NATIVE_PROFILE=DISTRIBUTION.
It carries its BLAS (on Linux and Windows the OpenMP OpenBLAS, BSD-3-Clause) with its
licence, and the licence texts of Kokkos, Kokkos Kernels, Kokkos-FFT, nanobind and
robin_map, which are compiled in. FFTW and the OpenMP runtime are bundled by the wheel
repair (auditwheel repair on Linux, delocate on macOS, delvewheel on Windows), and
their licence texts by SOLPROP_NATIVE_BUNDLED_LICENCES; FFTW (GPL-2.0-or-later) is
compatible with the package’s GPL-3.0-or-later. The published wheels’ recipe is in the
developer notes (Releasing and the native wheels). CUDA and
HIP builds link the vendors’ libraries and are built from source only.
The build profiles, CPU baselines and wheel recipes on this page were proposed and qualified by SEP 29.