Best practices
A condensed checklist. Each item links to the guide with the reasoning.
Structure
Section titled “Structure”- Import as
import cunumpy as xpand always callxp.<name>(...); neverfrom cunumpy import <array function>. (Backend-agnostic code) - Choose the backend once, in the entry point, with
CUNUMPY_BACKENDorset_backend(). Library code never callsset_backend(). (Choosing a backend) - Read back
xp.get_backend()after requesting CuPy; log it withxp.cuda.device_count()andxp.__version__. - Use
use_backend()for scoped switches (tests, CPU reference computations), never from several threads at once. - Functions that receive arrays follow them with
get_array_module(); functions that combine arrays check them withassert_same_backend().
- Create arrays with
xp.*so they are born on the right device. Usenumpydirectly only for host-only data and dtypes. - Convert explicitly, at boundaries:
to_cunumpy()on input,to_numpy()for output, plotting and host-only libraries. (Data movement) - Keep the time loop free of transfers and of host synchronization (
float(),.item(),print,ifon device values). Verify withassert_no_transfers()in a test. - Give dtypes explicitly for arrays that go to kernels.
- Generate random test data on the host with a seed, then convert; NumPy and CuPy generators differ.
Kernels
Section titled “Kernels”- Start with
KernelCatalog.from_package(..., missing_cuda="fallback"), port kernels by profile order, switch to"raise"when done. (Porting kernels) - Keep the CUDA kernel’s argument list identical to the host kernel’s; put both
in one folder, and test it with
catalog.check_signatures(). - Compile Pyccel host kernels ahead of time with the
pyccelcommand, or at run time withfrom_package(..., compile_host=...), and keep a NumPy version ashost_fallback. - Use
dispatch="arrays"if host arrays reach kernels while CuPy is active. - Ship
.cu/.cuhfiles as package data. - Declare
outputson host kernels so the fallback copies back only what was written, and never forget an argument that is written. - Pass device arrays to
CudaKernel; build argument objects once withas_device_array(). (Kernel arguments) - Use
long longindices,CUNUMPY_THREAD_1Dguards,conston inputs, andcunumpy_atomic_addfor scatter writes. - Use array views (
Array2D<double>) instead of hand-computed offsets for multi-dimensional data. - Generate structs from the host argument class (
CudaStruct.from_signature) and test that committed headers are up to date. - Compile at setup (
catalog.compile_all(jobs=...)). - Keep signature checks on; disable them (
check_signature=False) only for tiny kernels in hot loops after they are tested.
Verification
Section titled “Verification”- Parametrize tests with
BACKENDSor thebackendfixture; they run everywhere and use the GPU where there is one. (Testing kernels) - One
assert_kernels_agreetest overcatalog.parity_cases(). emulate_cuda_kerneltests, so CPU-only CI checks the CUDA arithmetic.- Seed
xp.rng.random_streamswith(seed, rank)and draw only from it. - Debug crashes with
CUNUMPY_CUDA_DEBUG=1, thencompute-sanitizer. (Debugging) - Time with
timed_region(), profile withnvtx_range()andnsys. (Profiling)
set_backend("cupy"),bind_local_device(), thenfrom mpi4py import MPI, thenrequire_cuda_aware_mpi(). (MPI)synchronize_for_mpi(*buffers)before every MPI call with device buffers.