Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/static_analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ jobs:
pip install ruff
ruff check src/ tests/

numpydoc:
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: actions/checkout@v4

- name: Docstring sections with numpydoc
run: |
pip install . numpydoc
python -m numpydoc lint $(git ls-files 'src/cunumpy/*.py' 'src/cunumpy/**/*.py' | grep -v _fake_cupy)

pylint:
runs-on: ubuntu-latest
continue-on-error: true
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,15 @@ jobs:
run: |
pytest .

- name: Run docstring examples
run: |
pytest --doctest-modules src/cunumpy

- name: Test docs build
run: |
pip install ".[docs]"
cd docs
make clean
make html
make html SPHINXOPTS="-W --keep-going"
cd ..
ls docs/build/html/index.html
63 changes: 57 additions & 6 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
variables:
CUDA_IMAGE: "gitlab-registry.mpcdf.mpg.de/mpcdf/ci-module-image/nvhpcsdk_26:2026"
ROCM_IMAGE: "gitlab-registry.mpcdf.mpg.de/mpcdf/ci-module-image/gcc_15-openmpi_5_0-rocm_7_1:2026"
PIP_DISABLE_PIP_VERSION_CHECK: "1"
CUNUMPY_BACKEND: "cupy"
CUNUMPY_REQUIRE_CUDA: "1"
# empty: the newest CuPy; gpu_tests_min_cupy pins the oldest supported one
CUPY_PIN: ""
# MI210 (MPCDF-GPU-05) is CDNA2, offload architecture gfx90a; also covers
# MI200/MI250(X). Change this (and the gpu-amd-gfx* tag below) for another
# AMD GPU generation.
HCC_AMDGPU_TARGET: "gfx90a"

stages:
- test

.gpu_job:
.gpu_job_nvidia:
stage: test
image: ${CUDA_IMAGE}
tags:
Expand All @@ -19,18 +26,62 @@ stages:
- module load fftw-serial/3.3.10
- nvidia-smi
# Keep NVRTC, CUDA libraries and headers on the same toolkit generation.
- python3 -m pip install --user "cupy-cuda13x[ctk]" "cuda-toolkit==13.2.*"
- python3 -m pip install --user "cupy-cuda13x[ctk]${CUPY_PIN}" "cuda-toolkit==13.2.*"
- python3 -m pip install --user -e ".[test]"
- export PATH="$HOME/.local/bin:$PATH"
- python3 -c "import cupy; cupy.show_config()"

gpu_tests:
extends: .gpu_job
# CuPy has no prebuilt ROCm wheel for recent ROCm (the old cupy-rocm-*
# packages stop at ROCm 4.3), so this builds it from source against the
# module-provided ROCm. HCC_AMDGPU_TARGET above must match the runner's GPU.
.gpu_job_amd:
stage: test
image: ${ROCM_IMAGE}
tags:
- gpu-amd
- gpu-amd-gfx90a
before_script:
- module load gcc/15
- module load openmpi/5.0
- module load rocm/7.1
- module load python-waterboa/2025.06
- rocm-smi
- export ROCM_HOME="${ROCM_PATH:-/opt/rocm}"
- export CUPY_INSTALL_USE_HIP=1
- python3 -m pip install --user "cupy${CUPY_PIN}"
- python3 -m pip install --user -e ".[test]"
- export PATH="$HOME/.local/bin:$PATH"
- python3 -c "import cupy; cupy.show_config()"
- python3 -c "import cupy; assert cupy.cuda.runtime.is_hip, 'expected a HIP build of CuPy'"

gpu_tests_nvidia:
extends: .gpu_job_nvidia
script:
- python3 -m pytest -q tests
- python3 testing/cupyx_names.py

# cunumpy/reduce.cuh and cunumpy/scan.cuh are not yet ported to HIP: tests that
# need them use `requires_warp_shuffle`, which skips (not fails) here even
# though CUNUMPY_REQUIRE_CUDA is set, see cunumpy.kernel_testing.
gpu_tests_amd:
extends: .gpu_job_amd
script:
- python3 -m pytest -q tests
- python3 testing/cupyx_names.py

# The oldest supported CuPy (cunumpy.MIN_CUPY_VERSION): the whole suite, and the
# fake cupyx.scipy must list exactly its names (_fake_cupyx.NAMES).
gpu_tests_min_cupy_nvidia:
extends: .gpu_job_nvidia
variables:
CUPY_PIN: "==14.0.0"
script:
- python3 -c "import cupy, cunumpy; assert cupy.__version__ == cunumpy.MIN_CUPY_VERSION, cupy.__version__"
- python3 -m pytest -q tests
- python3 testing/cupyx_names.py --exact

gpu_sanitizers:
extends: .gpu_job
gpu_sanitizers_nvidia:
extends: .gpu_job_nvidia
timeout: 45 minutes
parallel:
matrix:
Expand Down
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Supported CuPy versions: 14.0 or newer (`xp.MIN_CUPY_VERSION`,
`xp.cupy_version_supported`); an older CuPy warns when the CuPy backend is
first checked, and `backend_info()` reports the minimum. The GPU CI runs the
whole suite on CuPy 14.0.0 too (`gpu_tests_min_cupy`).
- MIT license (`LICENSE.txt`, referenced by `pyproject.toml`).
- `xp.optimize.newton`: Newton's method or the secant method on many
independent scalar equations at once, with the steps of
`scipy.optimize.newton` for an array `x0`, on the array's backend (no host
copies, fixed shapes, one synchronization per iteration).
- The fake CuPy has `cupyx.scipy`: SciPy behind the CuPy rules (host arrays
rejected, fake device arrays and proxy objects returned), so `xp.scipy` code
runs its CuPy path without a GPU. Every `cupyx.scipy` subpackage has only
the names the oldest supported CuPy (14.0.0) provides, so e.g. `special.jv`
or `sparse.csr_array` fails as on a GPU. `testing/cupyx_names.py` and
`tests/unit/test_cupyx_names.py` check these names against the installed
CuPy in the GPU CI.
`cupyx.empty_pinned`/`zeros_pinned` return NumPy arrays.
- `xp.optimize.fsolve`, `root`, `minimize` and `xp.integrate.quad`, `odeint`
(`cupyx.scipy` has neither): SciPy on the host with SciPy's arguments, the
callbacks get their arrays on the backend of `x0`/`y0`, and results come
back on that backend.
- `xp.scipy.linalg.solve_circulant` on the CuPy backend, which `cupyx.scipy`
lacks: SciPy's FFT solve and arguments, on the device.
- Solvers guide: splines on the device with `xp.scipy.interpolate`, and
batched root finding.
- Compiled Pyccel regression coverage for F-ordered arrays and column-block
copy-back, run in CPU CI with fake CuPy and required compilation.
- Publishing now requires CPU and GPU validation of the release commit, checks
the built distributions, and smoke-tests the wheel in a fresh environment.
- NumPy-format docstrings with runnable examples for the whole public API. The
API reference is generated from them (`docs/source/reference/`); the
hand-written `api.md` is now a short overview, and the CUDA headers have
their own page. CI checks the docstrings (ruff `D` rules, numpydoc
validation), runs their examples as doctests, and builds the docs with
warnings as errors. Docstring style: `docs/source/contributing.md`.

### Fixed
- Splines and other objects of the fake `cupyx.scipy` can be copied with
`copy.copy` and `copy.deepcopy` (also inside a copied container), as real
CuPy splines can.
- `xp.algorithms.segment_sum` and `SegmentPlan.sum` work on the fake CuPy: it
cannot compile the CUDA kernel, so the sum runs on the host buffers of the
fake arrays and the CuPy path of a program runs without a GPU.
- The ordering guide identifies 0.6.3 as the first release preserving F order
in CuPy-to-host conversions.
- `CudaStruct.from_signature` accepts quoted pyccel annotations
(`markers: "float[:, :]"`) in modules with `from __future__ import annotations`.

## [0.6.3] - 2026-10-08

Expand Down
21 changes: 21 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Max Lindqvist

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,9 @@ positional argument 0 is written, avoiding unnecessary copy-back for
read-only inputs. For a keyword call, declare the keyword name, such as
`outputs=("out",)`. A wrong declaration can leave GPU output values stale.
The wrapper can also traverse arrays nested in lists, tuples, dictionaries,
and selected application objects; see the full [API reference](docs/source/api.md)
for `object_modules`, `is_array`, aliasing, and output declarations.
and selected application objects; see [Host kernels with GPU
data](docs/source/kernels/pyccel-kernel.md) for `object_modules`, `is_array`,
aliasing, and output declarations.

## Write CUDA kernels next to host kernels

Expand Down Expand Up @@ -435,8 +436,9 @@ Launches can be 1D to 3D (`n_threads=(nx, ny)`, `block_size=(16, 16)`) or use
an explicit `grid`, with dynamic shared memory (`shared_mem`) and a `stream`.
C++ function templates are instantiated with `template_args`, and
`CudaKernelVariants` caches kernels whose source is generated per variant
(e.g. per dimension and dtype). See the [API reference](docs/source/api.md) for
details.
(e.g. per dimension and dtype). See [Writing CUDA
kernels](docs/source/kernels/cuda-kernel.md) and [CUDA
headers](docs/source/kernels/cuda-headers.md) for details.

Kernels run asynchronously, so a CUDA error (an illegal memory access, say)
normally surfaces at a later `.get()` or MPI call, far from the kernel that
Expand Down Expand Up @@ -523,8 +525,9 @@ published at <https://max-models.github.io/cunumpy/>:
* [Worked examples](docs/source/examples/index.md),
[best practices](docs/source/best-practices.md),
[troubleshooting](docs/source/troubleshooting.md),
[Pyodide](docs/source/pyodide.md) and the
[API reference](docs/source/api.md).
[Pyodide](docs/source/pyodide.md), the
[API overview](docs/source/api.md) and the
[API reference](docs/source/reference/index.md).

### For AI coding assistants

Expand Down
17 changes: 17 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Doctest setup: examples assume ``import cunumpy as xp`` and ``import numpy as np``."""

import numpy as np
import pytest

import cunumpy as xp


@pytest.fixture(autouse=True)
def _doctest_namespace(request, doctest_namespace):
if not isinstance(request.node, pytest.DoctestItem):
yield # leave the backend of the regular tests alone
return
doctest_namespace["np"] = np
doctest_namespace["xp"] = xp
with xp.use_backend("numpy"):
yield
Loading
Loading