Skip to content

Commit 7867891

Browse files
authored
Merge pull request #16 from max-models/11-add-a-one-way-mirror-to-mpcdf-gitlab-to-test-the-code-on-gpu
Added gitlab pipeline trigger
2 parents 8c5a79b + 89769ee commit 7867891

7 files changed

Lines changed: 242 additions & 186 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: Sync to GitLab and Run GPU CI
2+
3+
on:
4+
push:
5+
branches: [main, devel]
6+
pull_request:
7+
branches: [main, devel]
8+
workflow_dispatch:
9+
10+
jobs:
11+
sync-and-test:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout Code
15+
uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
19+
- name: Install SSH Key
20+
uses: webfactory/ssh-agent@v0.9.0
21+
with:
22+
ssh-private-key: ${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
23+
24+
- name: Push to GitLab via SSH & Provide Link
25+
run: |
26+
# 1. Setup SSH known hosts
27+
mkdir -p ~/.ssh
28+
ssh-keyscan gitlab.mpcdf.mpg.de >> ~/.ssh/known_hosts
29+
30+
# 2. Determine target branch
31+
if [ "${{ github.event_name }}" == "pull_request" ]; then
32+
TARGET_BRANCH="pr-${{ github.event.number }}"
33+
else
34+
TARGET_BRANCH="${{ github.ref_name }}"
35+
fi
36+
37+
# 3. Add GitLab SSH remote
38+
git remote add gitlab git@gitlab.mpcdf.mpg.de:maxlin/cunumpy.git
39+
40+
# 4. Force push (This automatically starts the GitLab Pipeline)
41+
git push -f gitlab HEAD:refs/heads/$TARGET_BRANCH
42+
43+
# 5. Provide the direct link
44+
# We construct the URL manually since the push triggers the pipeline automatically
45+
PIPELINE_URL="https://gitlab.mpcdf.mpg.de/maxlin/cunumpy/-/pipelines?ref=$TARGET_BRANCH"
46+
47+
echo "::notice::GitLab GPU CI Pipeline started automatically via Push!"
48+
echo "::notice::View Pipeline: $PIPELINE_URL"
49+

.gitlab-ci.yml

Lines changed: 38 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,40 @@
1-
# This file is a template, and might need editing before it works on your project.
2-
# To contribute improvements to CI/CD templates, please follow the Development guide at:
3-
# https://docs.gitlab.com/ee/development/cicd/templates.html
4-
# This specific template is located at:
5-
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Python.gitlab-ci.yml
6-
7-
# Official language image. Look for the different tagged releases at:
8-
# https://hub.docker.com/r/library/python/tags/
9-
image: python:latest
10-
11-
# Change pip's cache directory to be inside the project directory since we can
12-
# only cache local items.
131
variables:
14-
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
15-
16-
# https://pip.pypa.io/en/stable/topics/caching/
17-
cache:
18-
paths:
19-
- .cache/pip
20-
21-
before_script:
22-
- python --version ; pip --version # For debugging
23-
- pip install virtualenv
24-
- virtualenv venv
25-
- source venv/bin/activate
26-
27-
test:
2+
# Use the specialized MPCDF HPC image
3+
CUDA_IMAGE: "gitlab-registry.mpcdf.mpg.de/mpcdf/ci-module-image/nvhpcsdk_26:2026"
4+
PIP_DISABLE_PIP_VERSION_CHECK: "1"
5+
6+
stages:
7+
- test
8+
9+
gpu_tests:
10+
stage: test
11+
image: ${CUDA_IMAGE}
12+
tags:
13+
- gpu-nvidia
14+
- gpu-nvidia-cc80
15+
before_script:
16+
- module load python-waterboa/2025.06
17+
- module load nvhpcsdk/26
2818
script:
29-
- pip install ruff tox # you can also use tox
30-
- pip install --editable ".[test]"
31-
- tox -e py,ruff
32-
33-
run:
34-
script:
35-
- pip install .
36-
# run the command here
37-
artifacts:
38-
paths:
39-
- build/*
40-
41-
pages:
42-
script:
43-
- pip install sphinx sphinx-rtd-theme
44-
- cd doc
45-
- make html
46-
- mv build/html/ ../public/
47-
artifacts:
48-
paths:
49-
- public
50-
rules:
51-
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
52-
53-
deploy:
54-
stage: deploy
55-
script: echo "Define your deployment script!"
56-
environment: production
57-
19+
- echo "--- CUDA Sanity Check ---"
20+
- nvidia-smi
21+
22+
- echo "--- Detect Compute Capability ---"
23+
- nvidia-smi --query-gpu=compute_cap --format=csv,noheader
24+
25+
- echo "--- Tool Versions ---"
26+
- cmake --version
27+
- python3 --version
28+
- git --version
29+
30+
- echo "--- Pytest Execution ---"
31+
# The MPCDF image likely has a specific python environment.
32+
# We install our dependencies into the user directory or a virtualenv.
33+
- python3 -m pip install --user cupy-cuda12x
34+
- python3 -m pip install --user -e .
35+
36+
# Add the user bin to PATH for pytest
37+
- export PATH="$HOME/.local/bin:$PATH"
38+
- export ARRAY_BACKEND=cupy
39+
40+
- python3 -m pytest tests/unit/

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
- `xp.synchronize()`: Blocks until GPU operations are complete (no-op on CPU). Essential for accurate benchmarking.
2323
- **Developer Experience**:
2424
- Added `isort` configuration to `pyproject.toml` with `black` profile compatibility.
25+
- Reorganized test suite into specialized files: `test_numpy.py`, `test_cupy.py`, and `test_cunumpy.py`.
2526

2627
### Changed
2728
- **Dynamic Dispatch Architecture**: Refactored `src/cunumpy/xp.py` to use module-level `__getattr__`. This ensures that `cunumpy.<op>` calls always resolve to the currently active backend module, enabling seamless runtime switching via `set_backend`.

tests/unit/test_app.py

Lines changed: 0 additions & 131 deletions
This file was deleted.

tests/unit/test_cunumpy.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import numpy as np
2+
import pytest
3+
4+
import cunumpy as xp
5+
6+
7+
def test_to_numpy():
8+
arr = xp.array([1, 2, 3])
9+
# Even if it's already numpy, to_numpy should work
10+
arr_np = xp.to_numpy(arr)
11+
assert isinstance(arr_np, np.ndarray)
12+
assert np.array_equal(arr_np, [1, 2, 3])
13+
14+
15+
def test_to_cunumpy():
16+
arr = np.array([1, 2, 3])
17+
arr_xp = xp.to_cunumpy(arr)
18+
# Backend is numpy in tests usually
19+
assert isinstance(arr_xp, (np.ndarray, xp.ndarray))
20+
21+
22+
def test_get_backend_and_is_gpu_cpu():
23+
arr = np.array([1, 2, 3])
24+
assert xp.get_backend(arr) == "numpy"
25+
assert xp.is_gpu(arr) is False
26+
assert xp.is_cpu(arr) is True
27+
28+
29+
def test_use_backend():
30+
# Initial backend should be numpy (default) in this test environment
31+
assert "numpy" in xp.xp.__name__
32+
33+
with xp.use_backend("numpy"):
34+
assert "numpy" in xp.xp.__name__
35+
arr = xp.zeros(10)
36+
assert isinstance(arr, np.ndarray)
37+
38+
assert "numpy" in xp.xp.__name__
39+
40+
41+
def test_set_backend():
42+
# Set to numpy
43+
xp.set_backend("numpy")
44+
assert "numpy" in xp.xp.__name__
45+
arr = xp.array([1])
46+
assert isinstance(arr, np.ndarray)
47+
48+
# Set to cupy (falls back to numpy if not available)
49+
xp.set_backend("cupy")
50+
# If cupy is not installed, xp.xp will be numpy module
51+
arr2 = xp.array([2])
52+
assert arr2 is not None
53+
54+
55+
def test_backend_bools():
56+
with xp.use_backend("numpy"):
57+
assert xp.numpy_backend is True
58+
assert xp.cupy_backend is False

tests/unit/test_cupy.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import numpy as np
2+
import pytest
3+
4+
import cunumpy as xp
5+
6+
7+
def test_to_cupy_available():
8+
try:
9+
import cupy as cp
10+
except ImportError:
11+
pytest.skip("CuPy not installed")
12+
13+
with xp.use_backend("cupy"):
14+
arr = np.array([1, 2, 3])
15+
arr_cp = xp.to_cupy(arr)
16+
assert isinstance(arr_cp, cp.ndarray)
17+
18+
19+
def test_to_cupy_not_available():
20+
try:
21+
import cupy
22+
23+
pytest.skip("CuPy is installed, cannot test missing cupy error")
24+
except ImportError:
25+
pass
26+
27+
with xp.use_backend("cupy"):
28+
arr = np.array([1, 2, 3])
29+
with pytest.raises(ImportError):
30+
xp.to_cupy(arr)
31+
32+
33+
def test_synchronize():
34+
# Should not crash on any backend
35+
xp.synchronize()
36+
37+
with xp.use_backend("numpy"):
38+
xp.synchronize()
39+
40+
with xp.use_backend("cupy"):
41+
xp.synchronize()
42+
43+
44+
def test_xp_array_cupy():
45+
try:
46+
import cupy as cp
47+
except ImportError:
48+
pytest.skip("CuPy not installed")
49+
50+
with xp.use_backend("cupy"):
51+
arr = xp.array([1, 2])
52+
arr *= 2
53+
assert isinstance(arr, cp.ndarray)
54+
assert cp.asnumpy(arr).tolist() == [2, 4]

0 commit comments

Comments
 (0)