Skip to content
Open
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
7 changes: 7 additions & 0 deletions .github/workflows/test-wheel-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,13 @@ jobs:
CUDA_PATHFINDER_TEST_FIND_NVIDIA_BITCODE_LIB_STRICTNESS: all_must_work
run: run-tests pathfinder

- name: Run samples tests
if: ${{ inputs.test-mode == 'standard' }}
env:
CUDA_VER: ${{ matrix.CUDA_VER }}
LOCAL_CTK: ${{ matrix.LOCAL_CTK }}
run: run-tests samples

# ── Nightly: install wheels + optional dep together ──
- name: Install cuda-python wheels + PyTorch
if: ${{ inputs.test-mode == 'nightly-pytorch' }}
Expand Down
4 changes: 4 additions & 0 deletions .spdx-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ LICENSE
requirements*.txt
cuda_bindings/examples/*

# Samples are synced to NVIDIA/cuda-samples on release and carry the upstream
# verbose BSD-style copyright header instead of the SPDX identifiers.
samples/**/*.py

# Vendored
cuda_core/cuda/core/_include/dlpack.h
cuda_core/cuda/core/_include/aoti_shim.h
Expand Down
18 changes: 16 additions & 2 deletions ci/tools/run-tests
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ if [[ ${#} -ne 1 ]]; then
echo "Error: This script requires exactly 1 argument. You provided ${#}"
exit 1
fi
if [[ "${1}" != "bindings" && "${1}" != "core" && "${1}" != "pathfinder" && "${1}" != nightly-* ]]; then
echo "Error: Invalid test module '${1}'. Must be 'bindings', 'core', 'pathfinder', or 'nightly-*'"
if [[ "${1}" != "bindings" && "${1}" != "core" && "${1}" != "pathfinder" && "${1}" != "samples" && "${1}" != nightly-* ]]; then
echo "Error: Invalid test module '${1}'. Must be 'bindings', 'core', 'pathfinder', 'samples', or 'nightly-*'"
exit 1
fi

Expand Down Expand Up @@ -56,6 +56,20 @@ elif [[ "${test_module}" == "bindings" ]]; then
${SANITIZER_CMD} pytest -rxXs -v --durations=0 --randomly-dont-reorganize tests/cython
fi
popd
elif [[ "${test_module}" == "samples" ]]; then
# Samples re-use whatever cuda-bindings + cuda-core packages are already
# installed by an earlier ``core`` invocation. Install the cupy backend (the
# only test-only dep most samples need) and run the pytest harness.
TEST_CUDA_MAJOR="$(cut -d '.' -f 1 <<< "${CUDA_VER}")"
echo "Installing optional sample deps (cupy-cuda${TEST_CUDA_MAJOR}x, nvtx, pillow)"
pip install --upgrade pip
pip install \
"cupy-cuda${TEST_CUDA_MAJOR}x" \
nvtx \
pillow \
|| echo "Warning: optional sample deps install failed; affected samples will be waived"
echo "Running sample tests"
pytest -rxXs -v --durations=0 tests/samples/
elif [[ "${test_module}" == "core" || "${test_module}" == nightly-* ]]; then
# Shared setup for core and nightly modes.
TEST_CUDA_MAJOR="$(cut -d '.' -f 1 <<< ${CUDA_VER})"
Expand Down
4 changes: 4 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ def pytest_collection_modifyitems(config, items): # noqa: ARG001
if nodeid.startswith("tests/integration/") or "/tests/integration/" in nodeid:
item.add_marker(pytest.mark.smoke)

# Sample tests (orchestrator under tests/samples/, sample sources under samples/)
if nodeid.startswith("tests/samples/") or "/tests/samples/" in nodeid:
item.add_marker(pytest.mark.samples)

# Cython tests (any tests/cython subtree)
if (
"/tests/cython/" in nodeid
Expand Down
4,872 changes: 4,853 additions & 19 deletions cuda_core/pixi.lock

Large diffs are not rendered by default.

32 changes: 21 additions & 11 deletions cuda_core/pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,37 @@ cloudpickle = "*"
psutil = "*"
pyglet = "*"

[feature.examples.dependencies]
[feature.local-deps.dependencies]
cuda-bindings = { path = "../cuda_bindings" }
cuda-pathfinder = { path = "../cuda_pathfinder" }

[feature.samples.dependencies]
cuda-core = { path = "." }
cffi = "*"
pyglet = "*"
pytest = "*"
numpy = "*"

[feature.examples.system-requirements]
[feature.samples.system-requirements]
cuda = "13"

[feature.local-deps.dependencies]
cuda-bindings = { path = "../cuda_bindings" }
cuda-pathfinder = { path = "../cuda_pathfinder" }
[feature.samples.pypi-dependencies]
nvtx = "*"

[feature.examples.target.linux.dependencies]
[feature.samples.target.linux.dependencies]
cupy = "*"
pytorch-gpu = "*"
libgl-devel = "*"
gxx = "*"
pillow = "*"

[feature.examples.target.linux-64.activation.env]
[feature.samples.target.linux-64.activation.env]
CUDA_HOME = "$CONDA_PREFIX/targets/x86_64-linux"

[feature.examples.target.linux-aarch64.activation.env]
[feature.samples.target.linux-aarch64.activation.env]
CUDA_HOME = "$CONDA_PREFIX/targets/sbsa-linux"

[feature.examples.target.win-64.activation.env]
[feature.samples.target.win-64.activation.env]
CUDA_HOME = "$CONDA_PREFIX/Library"

[feature.cython-tests.dependencies]
Expand Down Expand Up @@ -115,7 +121,7 @@ nvidia-sphinx-theme = "*"
make = "*"

# We keep both cu12 and cu13 because cuda.core works with either major version.
# The local sibling checkouts are wired into the default/cu13/examples workflows;
# The local sibling checkouts are wired into the default/cu13/samples workflows;
# cu12 intentionally solves against published packages instead.
[environments]
default = { features = [
Expand All @@ -125,7 +131,7 @@ default = { features = [
], solve-group = "default" }
cu13 = { features = ["cu13", "test", "cython-tests", "local-deps"], solve-group = "cu13" }
cu12 = { features = ["cu12", "test", "cython-tests"], solve-group = "cu12" }
examples = { features = ["cu13", "examples", "local-deps"], solve-group = "examples" }
samples = { features = ["cu13", "samples", "local-deps"], solve-group = "samples" }
docs = { features = ["cu13", "docs", "local-deps"], solve-group = "docs" }

# TODO: check if these can be extracted from pyproject.toml
Expand Down Expand Up @@ -227,3 +233,7 @@ cmd = [
"norecursedirs=\"\"", # include cython tests (ignore by default config)
]
depends-on = [{ task = "build-cython-tests" }]

[tasks.test-samples]
cmd = ["pytest", "-rxXs", "-v", "$PIXI_PROJECT_ROOT/../tests/samples"]
default-environment = "samples"
7 changes: 7 additions & 0 deletions pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ cmd = [
'pixi run --manifest-path "$PIXI_PROJECT_ROOT/cuda_core" -e "$PIXI_ENVIRONMENT_NAME" test',
]

[target.linux.tasks.test-samples]
cmd = [
"bash",
"-c",
'pixi run --manifest-path "$PIXI_PROJECT_ROOT/cuda_core" test-samples',
]

[target.linux.tasks.test]
depends-on = [
{ task = "test-pathfinder" },
Expand Down
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ addopts = --showlocals
norecursedirs =
cuda_bindings/examples
cuda_core/examples
samples

testpaths =
cuda_pathfinder/tests
cuda_bindings/tests
cuda_core/tests
tests/integration
tests/samples
ci/tools/tests

markers =
pathfinder: tests for cuda_pathfinder
bindings: tests for cuda_bindings
core: tests for cuda_core
samples: tests for the standalone samples under ./samples
cython: cython tests
smoke: meta-level smoke tests
flaky: mark test as flaky (provided by pytest-rerunfailures)
Expand Down
13 changes: 13 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,19 @@ inline-quotes = "double"
"RUF059", # unused unpacked variable
]

"samples/**" = [
"T201", # print
"E402", # module-level import not at top of file (sys.path mutation before import)
"N801", # CUDA naming conventions (CamelCase classes)
"N802", # CUDA naming conventions (camelCase methods)
"N803", # CUDA naming conventions (argument names)
"N806", # non-lowercase variable in function (e.g. d_A, h_blockSums)
"N816", # mixed-case variable in global scope
"N999", # invalid module name (samples/Utilities is a directory, not a module)
"RUF001", # ambiguous unicode in strings (math symbols like ``×`` in shape annotations)
"RUF003", # ambiguous unicode in comments (same as RUF001)
]

"**/benchmarks/**" = [
"T201", # print
"RUF012", # mutable class default (ctypes _fields_ is standard)
Expand Down
134 changes: 134 additions & 0 deletions samples/Utilities/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# CUDA Python Utilities

Common utilities for CUDA Python samples using the `cuda.core` API.

## Overview

This module provides reusable utility functions for CUDA samples to reduce code duplication. Samples import from `cuda_samples_utils.py` using simple path-based imports (no package structure needed).

## Installation Requirements

Install from the Python samples directory:

```bash
cd /path/to/cuda-samples/Python
pip install -r requirements.txt
```

This installs a common CUDA 13 stack (see `python/requirements.txt`):

- `cuda-python` (>=13.0.0)
- `cuda-core` (>=1.0.0)
- `cupy-cuda13x` (>=14.0.0)
- `numpy` (>=2.3.2)

## How to Use in Samples

Import utilities using path-based import:

```python
import sys
from pathlib import Path

# Add Utilities directory to path
sys.path.insert(0, str(Path(__file__).parent.parent / "Utilities"))
from cuda_samples_utils import verify_array_result

# Use the utility
if verify_array_result(result, expected):
print("Success!")
```

## Available Functions

### Result Verification

#### `verify_array_result(result, expected, rtol=1e-5, atol=1e-8, verbose=True)`

Verify computed results match expected values. The helper detects whether both
arguments are NumPy arrays or both are CuPy arrays and uses the matching
library's `allclose` (no unnecessary cross-device transfers).

**Parameters:**
- `result`: NumPy or CuPy array with computed results
- `expected`: NumPy or CuPy array with expected values (same kind as `result`)
- `rtol`: Relative tolerance (default: 1e-5)
- `atol`: Absolute tolerance (default: 1e-8)
- `verbose`: Print test result (default: True)

**Returns:**
- `True` if results match within tolerance, `False` otherwise

**Example:**
```python
expected = a + b
if verify_array_result(c, expected):
print("Computation correct!")
```

### Package Check

#### `check_cuda_requirements()`

Check if required CUDA packages are available.

**Returns:**
- `True` if requirements are met, `False` otherwise

**Example:**
```python
if not check_cuda_requirements():
sys.exit(1)
```

## Design Philosophy

These utilities focus on common operations that are **not** part of `cuda.core` API:
- Result verification for NumPy or CuPy arrays
- Package requirements checking

For CUDA operations like device initialization, kernel compilation, and grid size calculations, samples should use `cuda.core` API directly to demonstrate the proper usage patterns.

## Complete Example

See `../vectorAdd/vectorAdd.py` for a complete example:

```python
import sys
from pathlib import Path

# Import utility
sys.path.insert(0, str(Path(__file__).parent.parent / "Utilities"))
from cuda_samples_utils import verify_array_result

import cupy as cp
from cuda.core import Device, Program, ProgramOptions, LaunchConfig, launch

# Use cuda.core directly for device and kernel operations
device = Device(0)
device.set_current()

program_options = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
program = Program(kernel_source, code_type="c++", options=program_options)
module = program.compile("cubin", name_expressions=("kernel_name",))
kernel = module.get_kernel("kernel_name")

# Calculate grid size inline
threads_per_block = 256
blocks_per_grid = (num_elements + threads_per_block - 1) // threads_per_block

# Launch kernel - pass cupy arrays directly
config = LaunchConfig(grid=blocks_per_grid, block=threads_per_block)
launch(stream, config, kernel, a, b, c, cp.int32(num_elements))

# Verify results using utility
verify_array_result(c, expected)
```

## Benefits

- **Code Reuse**: Write common functionality once
- **Consistency**: All samples use the same patterns
- **Maintainability**: Bug fixes benefit all samples
- **Transparency**: Samples show cuda.core API usage directly
- **Simplicity**: No complex package structure needed
47 changes: 47 additions & 0 deletions samples/Utilities/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of NVIDIA CORPORATION nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""
CUDA Python Samples - Utilities

Common utilities for CUDA Python samples.

Provides:
- Package requirements checking
- Result verification
"""

from .cuda_samples_utils import (
check_cuda_requirements,
verify_array_result,
)

__version__ = "1.0.0"

__all__ = [
"check_cuda_requirements",
"verify_array_result",
]
Loading