PyccelKernel adapts a function that expects NumPy arrays so it can be called
with CuPy arrays. It is named after Pyccel, whose compiled functions accept
only NumPy arrays, but it wraps any callable: a Numba function, a C extension,
or plain Python. It does not compile anything.
import cunumpy as xp
def smooth(field, out):
out[1:-1] = 0.25 * field[:-2] + 0.5 * field[1:-1] + 0.25 * field[2:]
out[0], out[-1] = field[0], field[-1]
smooth_kernel = xp.PyccelKernel(smooth, outputs=(1,))On every call it decides whether conversion is needed (the active backend is CuPy, or a CuPy array is among the arguments):
- No conversion (NumPy backend, NumPy arrays): the function is called directly. Identity, mutation, return values and exceptions behave as without the wrapper; the overhead is a type check per argument.
- Conversion: every CuPy array in the arguments is copied to the host, the function runs on the host copies, the arrays it may have written are copied back into the original CuPy arrays, and returned NumPy arrays are converted to CuPy.
with xp.use_backend("cupy"):
field = xp.random.random(1000)
out = xp.empty_like(field)
smooth_kernel(field, out) # out is updated on the deviceThe wrapper cannot know which arguments the function writes. By default it
copies back every converted array, which is correct but doubles the
transfers. outputs lists the arguments that may be written:
xp.PyccelKernel(smooth, outputs=(1,)) # positional argument 1
xp.PyccelKernel(update, outputs=(0, -1)) # first and last argument
xp.PyccelKernel(solve, outputs=("out",)) # solve(a, b, out=out)
xp.PyccelKernel(norm, outputs=()) # writes nothing- Positional arguments are declared by index (negative indices count from the end), keyword arguments by name. The two forms are not interchangeable, because compiled functions usually do not expose a Python signature that would map names to positions.
- A container or object declared as output has all its arrays copied back.
- A missing declaration is a silent bug: if the function writes an
argument that is not declared, the device array keeps its old values. When in
doubt, leave
outputs=None.
Lists, tuples and dicts are traversed recursively, so kernel([x, y], {"v": v})
works. Instances of your own classes are traversed only if their class's module
starts with one of the object_modules prefixes:
kernel = xp.PyccelKernel(push, object_modules=("my_simulation.",), outputs=(0,))
kernel(particles, dt) # particles.positions etc. are convertedSuch objects are shallow-copied and their array attributes replaced on the copy, so the caller's object keeps referencing the device arrays. Objects of other modules are passed through unchanged.
Other details:
- Aliasing is preserved. If the same device array appears twice, the host function sees one host array twice.
- Return values: NumPy arrays (also inside returned tuples and lists) are
converted to CuPy;
is_arraycustomizes which return values count as arrays (for NumPy subclasses). use_cupyforces conversion on (True) or off (False); the defaultNonedecides per call.- Argument objects with two forms (
KernelArguments) are replaced by their__host_args__()first; see Kernel arguments and structs.
Each converted call copies every input array to the host and every output back.
For a kernel called every time step with large arrays, this dominates the run
time. count_transfers() records one kernel_conversion event per converted
call, naming the kernel and the number of arrays:
with xp.count_transfers() as counter:
step(state, dt)
for event in counter.kernel_conversion_calls:
print(event.where, event.description)PyccelKernel is the right tool for:
- getting a code base to run on the GPU backend before any kernel is ported;
- kernels outside the time loop (setup, I/O, rare diagnostics);
- the fallback of a
Kernelthat has no CUDA version yet.
For kernels inside the time loop, write a CUDA version (Writing CUDA kernels) and pair it with the host kernel (Pairing host and CUDA kernels).
In Pyodide only the no-conversion path exists. Plain Python functions can still
be wrapped, so code that uses PyccelKernel runs in the browser; see
Pyodide.