CUDA 13.2 samples update (#432)

- Added Python samples for CUDA Python 1.0 release
- Renamed top-level `Samples` directory to `cpp` to accommodate Python samples.
This commit is contained in:
Dheemanth
2026-05-13 15:13:18 -07:00
committed by GitHub
parent a4526d5229
commit aeab82ff30
2072 changed files with 15077 additions and 1970 deletions

220
python/.gitignore vendored Normal file
View File

@@ -0,0 +1,220 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
*.lcov
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
# Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
# poetry.lock
# poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
# pdm.lock
# pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
# pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi/*
!.pixi/config.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule*
celerybeat.pid
# Redis
*.rdb
*.aof
*.pid
# RabbitMQ
mnesia/
rabbitmq/
rabbitmq-data/
# ActiveMQ
activemq-data/
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
# .idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Temporary file for partial code execution
tempCodeRunnerFile.py
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# Streamlit
.streamlit/secrets.toml

View File

@@ -0,0 +1,168 @@
# Sample: Image Blur with Unified Memory (Python)
## Description
Blur images on GPU using modern `cuda.core` APIs for kernel compilation, execution, and memory management. This sample demonstrates **zero-copy data sharing** between CPU and GPU using unified (managed) memory.
## What You'll Learn
- Compiling CUDA kernels at runtime with `cuda.core.Program`
- Launching kernels with `cuda.core.launch` and `LaunchConfig`
- Using unified memory with `cuda.core.ManagedMemoryResource`
- **Zero-copy CPU access** to unified memory via `np.from_dlpack()`
- Seamless CPU/GPU memory access without explicit transfers
## Key Concepts
### Kernel Compilation with cuda.core.Program
```python
# Compile CUDA C++ kernel at runtime
program = Program(KERNEL_CODE, code_type="c++", options=options)
compiled = program.compile(target_type="cubin")
kernel = compiled.get_kernel("box_blur_3x3")
```
### Kernel Launch with cuda.core.launch
```python
# Configure and launch kernel
config = LaunchConfig(grid=grid_size, block=block_size)
# Buffers can be passed directly as kernel arguments
launch(stream, config, kernel, src_buf, dst_buf, H, W)
```
### Unified Memory (Managed Memory)
This sample uses `ManagedMemoryResource` for simplicity: a single allocation is accessible from both CPU and GPU without explicit transfers. For performance-critical workloads, consider `LegacyPinnedMemoryResource` + `DeviceMemoryResource` instead, which gives explicit control over host/device placement and transfer costs.
Unified memory is accessible from both CPU and GPU without explicit data transfers:
```python
# Allocate unified memory
options = ManagedMemoryResourceOptions(preferred_location=device.device_id)
mr = ManagedMemoryResource(options)
src_buf = mr.allocate(n_bytes, stream)
dst_buf = mr.allocate(n_bytes, stream)
try:
# Synchronize to ensure allocations are complete before CPU access
stream.sync()
# Create numpy views of unified memory using DLPack protocol (zero-copy)
src_np = np.from_dlpack(src_buf).view(np.float32).reshape(H, W)
dst_np = np.from_dlpack(dst_buf).view(np.float32).reshape(H, W)
# CPU writes directly to unified memory
src_np[:] = input_data
# Launch kernel - buffers can be passed directly as arguments
launch(stream, config, kernel, src_buf, dst_buf, H, W)
stream.sync()
# Return zero-copy view; caller must close buffers when done
return dst_np, src_buf, dst_buf
except Exception:
src_buf.close()
dst_buf.close()
raise
```
When returning a zero-copy view, the caller must close the buffers after use (e.g., in a `try/finally` block) to avoid leaking managed memory.
## Key APIs
### From `cuda.core`:
- `Device` - CUDA device management
- `Program` - Runtime kernel compilation (NVRTC)
- `ProgramOptions` - Compilation options (architecture target)
- `LaunchConfig` - Kernel launch configuration (grid/block dimensions)
- `launch` - Execute compiled kernel
- `ManagedMemoryResource` - Unified memory allocation
### Zero-Copy Techniques:
- `np.from_dlpack(buffer)` - Create numpy view of unified memory using DLPack protocol
- Pass `buffer` directly to `launch()` as kernel arguments
- When returning a zero-copy view, return `(view, src_buf, dst_buf)` and have the caller close buffers in `try/finally` after use
## Kernel Techniques
- **2D Thread Mapping** - Each thread computes one output pixel
- **Stencil Pattern** - Read neighboring pixels (3x3 neighborhood)
- **Boundary Handling** - Clamp to edge for border pixels
- **Box Filter** - 3x3 averaging for blur effect
## Requirements
### Hardware:
- NVIDIA GPU with CUDA support
- Minimum GPU memory: 256 MB
### Software:
- CUDA Toolkit 13.0 or newer
- Python 3.10 or newer
- `cuda-python` package (13.0.0+)
- `cuda-core` package (>=0.6.0)
- `numpy` package (>=2.3.2)
- `pillow` package (10.0.0+)
## Installation
```bash
cd /path/to/cuda-samples/python/1_GettingStarted/blurImageUnifiedMemory
pip install -r requirements.txt
```
## How to Run
```bash
python blurImageUnifiedMemory.py
```
## Expected Output
```
============================================================
Image Blur with Unified Memory (cuda.core)
============================================================
Device: <Your GPU Name>
Compute Capability: sm_<XY>
Compiling CUDA kernel with cuda.core.Program...
Compiled for architecture: sm_<XY>
Image size: 256x256 grayscale
Creating sample image...
Blurring image on GPU...
Saving results...
Saved: original_image.png
Saved: blurred_image.png
Verifying result...
Test PASSED
Max difference from original: <value>
```
## Output Files
- `original_image.png` - Test pattern image before blur
- `blurred_image.png` - Image after 3x3 box blur
## Files
- `blurImageUnifiedMemory.py` - Python implementation using cuda.core
- `README.md` - This file
- `requirements.txt` - Sample dependencies
## See Also
- [cuda.core Documentation](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- [cuda.core.Program](https://nvidia.github.io/cuda-python/cuda-core/latest/generated/cuda.core.Program.html)
- [cuda.core.ManagedMemoryResource](https://nvidia.github.io/cuda-python/cuda-core/latest/generated/cuda.core.ManagedMemoryResource.html)
- [CUDA Managed Memory](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#um-unified-memory-programming-hd)

View File

@@ -0,0 +1,269 @@
# 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.
"""
Image Blur with Unified Memory using cuda.core
Demonstrates GPU image blurring using cuda.core APIs for kernel compilation,
launch, and unified memory allocation.
"""
import sys
try:
import numpy as np
from cuda.core import (
Device,
LaunchConfig,
ManagedMemoryResource,
ManagedMemoryResourceOptions,
Program,
ProgramOptions,
launch,
)
from PIL import Image
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
# CUDA kernel source code - compiled at runtime by cuda.core.Program
BOX_BLUR_KERNEL_CODE = r"""
extern "C" __global__
void box_blur_3x3(const float* __restrict__ src,
float* __restrict__ dst, int H, int W) {
/*
* Simple 3x3 box blur CUDA kernel.
*
* Each thread computes one output pixel by averaging
* the 3x3 neighborhood of input pixels (stencil pattern).
*/
int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
if (x >= W || y >= H) return;
float sum = 0.0f;
int count = 0;
// 3x3 stencil: iterate over neighborhood
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
int nx = x + dx;
int ny = y + dy;
// Boundary check (clamp to edge)
if (nx >= 0 && nx < W && ny >= 0 && ny < H) {
sum += src[ny * W + nx];
count++;
}
}
}
dst[y * W + x] = sum / count;
}
"""
def make_test_image(h: int, w: int, dtype=np.uint8) -> np.ndarray:
"""Create a test grayscale image for demonstration."""
img = np.zeros((h, w), dtype=dtype)
# Create horizontal stripes
for i in range(0, h, 50):
img[i : i + 25, :] = 255
# Create vertical stripes with different intensity
for j in range(0, w, 50):
img[:, j : j + 25] = 128
# Add circular pattern for interesting blur effects
center_y, center_x = h // 2, w // 2
y, x = np.ogrid[:h, :w]
circle_mask = (x - center_x) ** 2 + (y - center_y) ** 2 <= (min(h, w) // 6) ** 2
img[circle_mask] = 200
return np.ascontiguousarray(img)
def blur_image_unified_memory(
host_np: np.ndarray, device: Device, stream, kernel
) -> tuple[np.ndarray, object, object]:
"""
Blur image on GPU using unified memory with cuda.core.
This function demonstrates:
1. Allocate managed memory using ManagedMemoryResource
2. Create zero-copy numpy views using np.from_dlpack()
3. Launch kernel via cuda.core.launch
Args:
host_np: NumPy array containing image data on CPU
device: CUDA device to use
stream: cuda.core Stream for async operations
kernel: Compiled cuda.core Kernel object
Returns:
Tuple of (dst_np, src_buf, dst_buf). dst_np is a zero-copy view into
unified memory. Caller must close src_buf and dst_buf when done with
dst_np to avoid leaking managed memory.
"""
H, W = host_np.shape
n_bytes = H * W * np.dtype(np.float32).itemsize
# Create managed memory resource for unified memory allocation
options = ManagedMemoryResourceOptions(preferred_location=device.device_id)
mr = ManagedMemoryResource(options)
# Allocate unified memory buffers for source and destination images
src_buf = mr.allocate(n_bytes, stream)
dst_buf = mr.allocate(n_bytes, stream)
try:
# Synchronize to ensure allocations are complete before CPU access
stream.sync()
# Create numpy views of unified memory using DLPack protocol (zero-copy)
src_np = np.from_dlpack(src_buf).view(np.float32).reshape(H, W)
dst_np = np.from_dlpack(dst_buf).view(np.float32).reshape(H, W)
# Write input data to unified memory (CPU can access directly)
src_np[:] = host_np.astype(np.float32) / 255.0
# Configure kernel launch parameters
block_size = (16, 16)
grid_size = (
(W + block_size[0] - 1) // block_size[0],
(H + block_size[1] - 1) // block_size[1],
)
# Create LaunchConfig for kernel execution
config = LaunchConfig(grid=grid_size, block=block_size)
# Launch kernel - buffers can be passed directly as kernel arguments
launch(
stream,
config,
kernel,
src_buf,
dst_buf,
np.int32(H),
np.int32(W),
)
# Synchronize to ensure kernel completion before reading results
stream.sync()
# Return zero-copy view; caller closes buffers when done
return (dst_np, src_buf, dst_buf)
except Exception:
src_buf.close()
dst_buf.close()
raise
def main():
"""
Complete demonstration of GPU image blurring with cuda.core.
This example shows:
1. Device initialization with cuda.core.Device
2. Kernel compilation with cuda.core.Program
3. Unified memory with cuda.core.ManagedMemoryResource
4. Kernel launch with cuda.core.launch and LaunchConfig
"""
print("=" * 60)
print("Image Blur with Unified Memory (cuda.core)")
print("=" * 60)
# Initialize CUDA device
device = Device(0)
device.set_current()
print(f"\nDevice: {device.name}")
print(f"Compute Capability: sm_{device.arch}")
# Create stream for async operations
stream = device.create_stream()
try:
# Compile kernel using cuda.core.Program
print("\nCompiling CUDA kernel with cuda.core.Program...")
arch = f"sm_{device.arch}"
options = ProgramOptions(arch=arch)
program = Program(BOX_BLUR_KERNEL_CODE, code_type="c++", options=options)
compiled = program.compile(target_type="cubin")
kernel = compiled.get_kernel("box_blur_3x3")
print(f" Compiled for architecture: {arch}")
# Image parameters
H, W = 256, 256
print(f"\nImage size: {H}x{W} grayscale")
# Create test image
print("Creating sample image...")
host_np = make_test_image(H, W, dtype=np.uint8)
# Blur image on GPU using cuda.core (returns zero-copy view + buffers)
print("Blurring image on GPU...")
blurred_result, src_buf, dst_buf = blur_image_unified_memory(
host_np, device, stream, kernel
)
try:
# Save images (use zero-copy view before releasing buffers)
print("\nSaving results...")
original_pil = Image.fromarray(host_np, mode="L")
original_pil.save("original_image.png")
print(" Saved: original_image.png")
blurred_uint8 = (np.clip(blurred_result, 0, 1) * 255).astype(np.uint8)
blurred_pil = Image.fromarray(blurred_uint8, mode="L")
blurred_pil.save("blurred_image.png")
print(" Saved: blurred_image.png")
# Verify blur was applied
print("\nVerifying result...")
original_float = host_np.astype(np.float32) / 255.0
max_diff = np.max(np.abs(blurred_result - original_float))
blur_applied = max_diff > 0.01
if blur_applied:
print(" Test PASSED")
else:
print(" Test FAILED - blur not applied")
sys.exit(1)
print(f" Max difference from original: {max_diff:.4f}")
finally:
src_buf.close()
dst_buf.close()
finally:
stream.close()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,6 @@
# Image Blur with Unified Memory Sample Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0
numpy>=2.3.2
pillow>=10.0.0

View File

@@ -0,0 +1,119 @@
# Sample: Image Array Copy to GPU (Python)
## Description
Copy image arrays between CPU and GPU memory using the modern `cuda.core` API with optimal performance through pinned memory and asynchronous transfers.
## What You'll Learn
- How to use pinned memory for faster CPU↔GPU transfers
- Using the `cuda.core` API for memory management
- Working with DLPack for zero-copy array views
- Performing asynchronous memory transfers with CUDA streams
- Interoperability between CUDA Core API and CuPy
- Proper CUDA resource management and cleanup
## Key Libraries
- `cuda.core` - Modern CUDA Python API
- `numpy` - Array operations and DLPack support
- `cupy` - GPU array operations and CUDA interoperability
## Key APIs
### From `cuda.core`:
- `Device()` - Initialize and access CUDA device
- `Device.set_current()` - Set the current device for API calls
- `Device.create_stream()` - Create CUDA stream for async operations
- `Device.memory_resource` - Access device memory allocator
- `PinnedMemoryResource()` - Allocate pinned host memory
- `buffer.copy_to()` - Copy data between memory spaces
- `buffer.close()` - Release allocated memory
### From `numpy`:
- `np.from_dlpack()` - Create array view from DLPack capsule
- `np.copyto()` - Copy data between arrays
### From `cupy`:
- `cp.from_dlpack()` - Create GPU array view from DLPack capsule
- `cp.cuda.ExternalStream()` - Use external CUDA stream
### From `cuda_samples_utils`:
- `verify_array_result()` - Verify computation results
## Requirements
### Hardware:
- NVIDIA GPU with CUDA support
- Sufficient GPU memory for image data (sample uses ~200KB for 256×256×3 image)
### Software:
- CUDA Toolkit 13.0 or newer
- Python 3.10 or newer
- NumPy 2.3.2 or newer (required for DLPack support)
- `cuda-python` package (>=13.0.0+)
- `cuda-core` package (>=0.6.0)
- `cupy-cuda13x` package (13.0.0+)
## Installation
Install the required packages from requirements.txt:
```bash
cd /path/to/cuda-samples/python/1_GettingStarted/copyImageArraytoGPU
pip install -r requirements.txt
```
The requirements.txt installs:
- `numpy` (2.3.2+, required for DLPack)
- `cuda-python` (>=13.0.0+)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (13.0.0+)
## How to Run
### Basic usage:
```bash
cd samples/python/1_GettingStarted/copyImageArraytoGPU
python copyImageArraytoGPU.py
```
## Expected Output
```
[Image Array Copy to GPU using CUDA Core API]
Device: NVIDIA GeForce RTX 4090
[Image array copy of 256x256x3 image]
Creating sample image...
Copying image to GPU...
Creating CuPy view of GPU data...
Mean pixel value (computed on GPU): 127.50
Copying image back from GPU...
Verifying result...
Test PASSED
Done
```
**Note:** Device name will vary based on your GPU.
## Files
- `copyImageArraytoGPU.py` - Python implementation using cuda.core API
- `README.md` - This file
- `requirements.txt` - Sample dependencies
- `../../Utilities/cuda_samples_utils.py` - Common utilities (imported by this sample)
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [cuda.core API Guide](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- [DLPack Specification](https://dmlc.github.io/dlpack/latest/)
- [CuPy Documentation](https://docs.cupy.dev/)

View File

@@ -0,0 +1,239 @@
# 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.
"""
Image Array Copy to GPU using CUDA Core API
This sample demonstrates how to copy image arrays between CPU and GPU memory
using NVIDIA's CUDA Core Python API with optimal performance.
"""
import sys
from pathlib import Path
# Add parent directory to path to import utilities
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
from cuda_samples_utils import verify_array_result
try:
import cupy as cp
import numpy as np
from cuda.core import Buffer, Device, PinnedMemoryResource, Stream
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
# ----------------------------- Helper Functions ------------------------------
def make_random_image(h: int, w: int, c: int, dtype=np.uint8) -> np.ndarray:
"""
Create a random test image for demonstration.
Args:
h: Image height in pixels
w: Image width in pixels
c: Number of channels (e.g., 3 for RGB)
dtype: NumPy data type (e.g., np.uint8 for 0-255 pixel values)
Returns:
A contiguous NumPy array representing the image
"""
img = np.random.randint(0, 256, size=(h, w, c), dtype=dtype)
return np.ascontiguousarray(img) # Ensure memory is contiguous for GPU transfer
# ----------------------------- Core GPU Functions ---------------------------
def copy_image_to_gpu_cuda_core(
host_np: np.ndarray, dev: Device, stream: Stream
) -> tuple[Buffer, Buffer]:
"""
Copy image from CPU memory to GPU memory using optimal transfer method.
This function demonstrates the recommended approach:
1. Use pinned memory for faster transfers
2. Use DLPack for zero-copy array views
3. Perform async transfers on a CUDA stream
Args:
host_np: NumPy array containing image data on CPU
dev: CUDA device object
stream: CUDA stream for async operations
Returns:
Tuple of (device_buffer, pinned_buffer) - both need to be cleaned up later
"""
nbytes = host_np.nbytes # Calculate total bytes needed
# Step 1: Set up memory resources
# Device memory resource - allocates on GPU
device_mr = dev.memory_resource
# Pinned memory resource - allocates CPU memory that GPU can access faster
pinned_mr = PinnedMemoryResource()
# Step 2: Allocate memory buffers
pinned_buffer = pinned_mr.allocate(nbytes, stream=stream) # Fast CPU memory
device_buffer = device_mr.allocate(nbytes, stream=stream) # GPU memory
# Step 3: Create a NumPy view of pinned memory using DLPack
# This allows us to work with pinned memory as if it's a regular NumPy array
pinned_view = (
np.from_dlpack(pinned_buffer).view(dtype=host_np.dtype).reshape(host_np.shape)
)
# Step 4: Copy image data from regular CPU memory to pinned CPU memory
# This is a CPU-to-CPU copy, so it's very fast
np.copyto(pinned_view, host_np)
# Step 5: Copy from pinned CPU memory to GPU memory
# This is the actual CPU-to-GPU transfer, done asynchronously
pinned_buffer.copy_to(device_buffer, stream=stream)
return device_buffer, pinned_buffer
def copy_image_from_gpu_cuda_core(
device_buffer: Buffer, shape: tuple, dtype: type, dev: Device, stream: Stream
) -> np.ndarray:
"""
Copy image from GPU memory back to CPU memory.
This function reverses the GPU-to-CPU transfer process:
1. Allocate pinned CPU memory for fast transfer
2. Copy from GPU to pinned CPU memory
3. Create NumPy view and copy to regular CPU memory
Args:
device_buffer: GPU buffer containing image data
shape: Original image shape tuple (height, width, channels)
dtype: Original image data type
dev: CUDA device object
stream: CUDA stream for async operations
Returns:
NumPy array with image data copied from GPU
"""
nbytes = np.prod(shape) * np.dtype(dtype).itemsize # Calculate total bytes
# Step 1: Create pinned memory for fast GPU-to-CPU transfer
pinned_mr = PinnedMemoryResource()
pinned_buffer = pinned_mr.allocate(nbytes, stream=stream)
# Step 2: Copy from GPU memory to pinned CPU memory
device_buffer.copy_to(pinned_buffer, stream=stream)
stream.sync() # Wait for the GPU transfer to complete
# Step 3: Create NumPy view of pinned memory using DLPack
pinned_view = np.from_dlpack(pinned_buffer).view(dtype=dtype).reshape(shape)
# Step 4: Copy from pinned CPU memory to regular CPU memory
# This creates the final result that can be used normally
host_result = pinned_view.copy()
# Step 5: Clean up the temporary pinned buffer
pinned_buffer.close(stream)
return host_result
# ------------------------------ Main Demo ------------------------------------
def main():
"""
Complete demonstration of GPU image copying workflow.
This example shows:
1. Setting up CUDA device and stream
2. Creating a sample image
3. Copying image to GPU
4. Accessing GPU data with CuPy (optional)
5. Copying image back from GPU
6. Verifying data integrity
7. Proper cleanup of resources
"""
print("[Image Array Copy to GPU using CUDA Core API]")
# Image parameters - modify these to test different sizes
H, W, C = 256, 256, 3 # Height=256, Width=256, Channels=3 (RGB)
dtype = np.uint8 # Standard image pixel type (0-255 values)
# Step 1: Set up CUDA device and stream
dev = Device() # Get default CUDA device (GPU 0)
dev.set_current() # Make this device the active one
stream = dev.create_stream() # Create stream for async operations
print(f"Device: {dev.name}")
print(f"[Image array copy of {H}x{W}x{C} image]")
# Step 2: Configure CuPy to use our CUDA stream (for interoperability)
cp.cuda.ExternalStream(int(stream.handle)).use()
# Step 3: Create a test image on CPU
print("Creating sample image...")
host_np = make_random_image(H, W, C, dtype=dtype)
# Step 4: Copy image from CPU to GPU
print("Copying image to GPU...")
device_buffer, pinned_buffer = copy_image_to_gpu_cuda_core(host_np, dev, stream)
# Step 5: (Optional) Get a CuPy view of GPU data for processing
# This shows how you can work with the GPU data without copying it back
print("Creating CuPy view of GPU data...")
device_cp = cp.from_dlpack(device_buffer).view(dtype=dtype).reshape(H, W, C)
# Example: compute mean pixel value on GPU
mean_value = float(cp.mean(device_cp))
print(f"Mean pixel value (computed on GPU): {mean_value:.2f}")
# Step 6: Copy image back from GPU to CPU
print("Copying image back from GPU...")
host_back = copy_image_from_gpu_cuda_core(
device_buffer, host_np.shape, host_np.dtype, dev, stream
)
# Step 7: Verify that the data survived the round trip
print("Verifying result...")
host_back_cp = cp.asarray(host_back)
host_np_cp = cp.asarray(host_np)
verify_array_result(host_back_cp, host_np_cp, rtol=0, atol=0)
# Step 8: Clean up all allocated resources
device_buffer.close(stream) # Free GPU memory
pinned_buffer.close(stream) # Free pinned CPU memory
stream.close() # Close CUDA stream
cp.cuda.Stream.null.use() # Reset CuPy's stream to default
print("\nDone")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,6 @@
# Image Array Copy to GPU Sample Requirements
numpy>=2.3.2
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0

View File

@@ -0,0 +1,189 @@
# Sample: Device Query (Python)
## Description
Query and display detailed properties of all CUDA-capable devices in your system using the modern `cuda.core` API.
## What You'll Learn
- How to enumerate CUDA devices in the system
- Using the `cuda.core` API for device management
- Querying comprehensive device properties (compute capability, memory, limits)
- Accessing low-level device attributes via `cuda.bindings`
- Checking peer-to-peer (P2P) access capabilities between GPUs
## Key Libraries
- `cuda.core` - Modern CUDA Python API
- `cuda.bindings` - Low-level CUDA bindings for runtime and driver APIs
## Key APIs
### From `cuda.core`:
- `Device.get_all_devices()` - Get tuple of all available Device instances
- `Device(device_id)` - Get Device object for specific device ID
- `system.get_driver_version()` - Query CUDA driver version
- `Device.set_current()` - Set the current device for API calls
- `Device.properties` - Access comprehensive device properties
- `Device.name` - Get device name string
- `Device.can_access_peer()` - Check P2P access to peer device
### From `cuda.bindings.runtime`:
- `cudart.cudaRuntimeGetVersion()` - Get CUDA runtime version
- `cudart.cudaDeviceGetAttribute()` - Query specific device attributes
### From `cuda.bindings.driver`:
- `cuda.cuMemGetInfo()` - Get memory information for current device
## Device Properties Queried
### Compute Capabilities:
- Compute capability version (major.minor)
- Driver and runtime versions
- Number of multiprocessors and CUDA cores
### Memory Information:
- Total global memory
- Memory clock rate and bus width
- L2 cache size
- Constant and shared memory sizes
- Maximum memory pitch
### Execution Configuration Limits:
- Maximum threads per block and per multiprocessor
- Maximum block dimensions (x, y, z)
- Maximum grid dimensions (x, y, z)
- Warp size
- Registers per block
### Texture Capabilities:
- Maximum texture dimensions (1D, 2D, 3D)
- Maximum layered texture sizes
### Feature Support:
- Unified Addressing (UVA)
- Managed Memory
- Compute Preemption
- Cooperative Kernel Launch
- ECC support
- Host page-locked memory mapping
- Concurrent copy and kernel execution
### System Information:
- PCI bus information
- Compute mode
- Driver mode (Windows only)
- P2P access matrix (multi-GPU systems)
## Requirements
### Hardware:
- NVIDIA GPU with CUDA support (any compute capability)
- No specific GPU memory requirement (query only)
### Software:
- CUDA Toolkit 13.0 or newer (recommended; matches `cuda-python` 13.x)
- Python 3.10 or newer
- `cuda-python` package (>=13.0.0)
- `cuda-core` package (>=0.6.0)
## Installation
Install the required packages from requirements.txt:
```bash
cd cuda-samples/python/1_GettingStarted/deviceQuery
pip install -r requirements.txt
```
The requirements.txt installs:
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
## How to Run
### Basic usage:
```bash
cd cuda-samples/python/1_GettingStarted/deviceQuery
python deviceQuery.py
```
### Skip P2P information:
```bash
python deviceQuery.py --no-p2p
```
## Expected Output
```
[CUDA Device Query using CUDA Core API]
Detected 1 CUDA Capable device(s)
Device 0: <Your GPU Name>
CUDA Driver Version / Runtime Version 12.4 / 12.6
CUDA Capability Major/Minor version number: 8.9
Total amount of global memory: 24217 MBytes (25393954816 bytes)
(132) Multiprocessors, (128) CUDA Cores/MP: 16896 CUDA Cores
GPU Max Clock rate: 1980 MHz (1.98 GHz)
Memory Clock rate: 10501 Mhz
Memory Bus Width: 384-bit
L2 Cache Size: 67108864 bytes
Maximum Texture Dimension Size (x,y,z) 1D=(131072), 2D=(131072, 65536), 3D=(16384, 16384, 16384)
Maximum Layered 1D Texture Size, (num) layers 1D=(32768), 2048 layers
Maximum Layered 2D Texture Size, (num) layers 2D=(32768, 32768), 2048 layers
Total amount of constant memory: 65536 bytes
Total amount of shared memory per block: 49152 bytes
Total shared memory per multiprocessor: 102400 bytes
Total number of registers available per block: 65536
Warp size: 32
Maximum number of threads per multiprocessor: 1536
Maximum number of threads per block: 1024
Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)
Maximum memory pitch: 2147483647 bytes
Texture alignment: 512 bytes
Concurrent copy and kernel execution: Yes with 2 copy engine(s)
Run time limit on kernels: Yes
Integrated GPU sharing Host Memory: No
Support host page-locked memory mapping: Yes
Device has ECC support: Enabled
Device supports Unified Addressing (UVA): Yes
Device supports Managed Memory: Yes
Device supports Compute Preemption: Yes
Supports Cooperative Kernel Launch: Yes
Device PCI Domain ID / Bus ID / location ID: 0 / 1 / 0
Compute Mode:
< Default (multiple host threads can use cudaSetDevice() with device simultaneously) >
Done
```
**Note:** Output will vary based on your specific GPU model and system configuration.
For multi-GPU systems, the output will include information for all detected devices and a P2P access matrix showing which GPUs can directly access each other's memory.
## Files
- `deviceQuery.py` - Python implementation using cuda.core API
- `requirements.txt` - Sample dependencies
## Use Cases
- **System Diagnostics** - Verify CUDA installation and GPU detection
- **Hardware Profiling** - Understand GPU capabilities before optimization
- **Multi-GPU Systems** - Identify P2P topology for optimal data placement
- **Kernel Development** - Determine execution configuration limits
- **Compatibility Checks** - Verify compute capability requirements
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [cuda.core API Guide](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- [CUDA Programming Guide - Device Information](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-enumeration)

View File

@@ -0,0 +1,389 @@
# 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.
"""
Device Query using CUDA Core API
This sample enumerates the properties of the CUDA devices present in the system.
"""
import platform
import sys
# cuda.bindings used for properties not yet exposed in cuda.core (see comments below)
try:
from cuda.bindings import driver as cuda, runtime as cudart
from cuda.core import Device, system
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
def print_property(label, value, indent=2):
"""
Helper function to print device properties with aligned formatting.
Parameters
----------
label : str
Property label
value : any
Property value
indent : int
Number of spaces for indentation (default: 2)
"""
field_width = 47
spaces = " " * indent
print(f"{spaces}{label:<{field_width}}{value}")
def fmt_bytes(size_in_bytes):
"""Format bytes to human-readable string with MBytes."""
return f"{size_in_bytes / (1024 * 1024):.0f} MBytes ({size_in_bytes} bytes)"
def fmt_hz(rate_in_khz):
"""Format frequency in kHz to MHz and GHz."""
return f"{rate_in_khz * 1e-3:.0f} MHz ({rate_in_khz * 1e-6:.2f} GHz)"
def fmt_yes_no(val):
"""Format boolean value to Yes/No string."""
return "Yes" if val else "No"
def convert_sm_ver_to_cores(major, minor):
"""
Maps SM version to the number of CUDA cores per SM.
Information taken from:
https://github.com/NVIDIA/cuda-samples/blob/master/Common/helper_cuda.h
Parameters
----------
major : int
Major compute capability version
minor : int
Minor compute capability version
Returns
-------
int
Number of CUDA cores per SM, or 0 if unknown
"""
sm_to_cores = {
(3, 0): 192,
(3, 2): 192,
(3, 5): 192,
(3, 7): 192,
(5, 0): 128,
(5, 2): 128,
(5, 3): 128,
(6, 0): 64,
(6, 1): 128,
(6, 2): 128,
(7, 0): 64,
(7, 2): 64,
(7, 5): 64,
(8, 0): 64,
(8, 6): 128,
(8, 7): 128,
(8, 9): 128,
(9, 0): 128,
(10, 0): 128,
(10, 1): 128,
(10, 3): 128,
(11, 0): 128,
(12, 0): 128,
(12, 1): 128,
}
return sm_to_cores.get((major, minor), 0)
def print_device_info(dev_id, device):
"""
Print detailed information for a single CUDA device.
Uses device.properties (cuda.core) for most fields; cuda.bindings for
runtime version and global memory (not yet in high-level API).
"""
device.set_current()
props = device.properties
print()
print(f"Device {dev_id}: {device.name}")
# cuda.bindings workaround: runtime version not in cuda.core
driver_major, driver_minor = system.get_driver_version()
err, runtime_version = cudart.cudaRuntimeGetVersion()
if err != cudart.cudaError_t.cudaSuccess:
raise RuntimeError(f"Failed to get CUDA runtime version: {err}")
runtime_major = runtime_version // 1000
runtime_minor = (runtime_version % 1000) // 10
print_property(
"CUDA Driver Version / Runtime Version",
f"{driver_major}.{driver_minor} / {runtime_major}.{runtime_minor}",
)
print_property(
"CUDA Capability Major/Minor version number:",
f"{props.compute_capability_major}.{props.compute_capability_minor}",
)
# cuda.bindings workaround: global memory (free/total) not in device.properties
err, free_mem, total_mem_bytes = cuda.cuMemGetInfo()
if err != cuda.CUresult.CUDA_SUCCESS:
raise RuntimeError(f"Failed to get memory info: {err}")
print_property("Total amount of global memory:", fmt_bytes(total_mem_bytes))
sm_cores = convert_sm_ver_to_cores(
props.compute_capability_major, props.compute_capability_minor
)
total_cores = sm_cores * props.multiprocessor_count
print_property(
f"({props.multiprocessor_count:3d}) Multiprocessors, "
f"({sm_cores:3d}) CUDA Cores/MP:",
f"{total_cores} CUDA Cores",
)
print_property("GPU Max Clock rate:", fmt_hz(props.clock_rate))
print_property("Memory Clock rate:", f"{props.memory_clock_rate * 1e-3:.0f} Mhz")
print_property("Memory Bus Width:", f"{props.global_memory_bus_width}-bit")
if props.l2_cache_size > 0:
print_property("L2 Cache Size:", f"{props.l2_cache_size} bytes")
print_property(
"Maximum Texture Dimension Size (x,y,z)",
f"1D=({props.maximum_texture1d_width}), "
f"2D=({props.maximum_texture2d_width}, {props.maximum_texture2d_height}), "
f"3D=({props.maximum_texture3d_width}, {props.maximum_texture3d_height}, "
f"{props.maximum_texture3d_depth})",
)
print_property(
"Maximum Layered 1D Texture Size, (num) layers",
f"1D=({props.maximum_texture1d_layered_width}), "
f"{props.maximum_texture1d_layered_layers} layers",
)
print_property(
"Maximum Layered 2D Texture Size, (num) layers",
f"2D=({props.maximum_texture2d_layered_width}, "
f"{props.maximum_texture2d_layered_height}), "
f"{props.maximum_texture2d_layered_layers} layers",
)
print_property(
"Total amount of constant memory:", f"{props.total_constant_memory} bytes"
)
print_property(
"Total amount of shared memory per block:",
f"{props.max_shared_memory_per_block} bytes",
)
print_property(
"Total shared memory per multiprocessor:",
f"{props.max_shared_memory_per_multiprocessor} bytes",
)
print_property(
"Total number of registers available per block:", props.max_registers_per_block
)
print_property("Warp size:", props.warp_size)
print_property(
"Maximum number of threads per multiprocessor:",
props.max_threads_per_multiprocessor,
)
print_property("Maximum number of threads per block:", props.max_threads_per_block)
print_property(
"Max dimension size of a thread block (x,y,z):",
f"({props.max_block_dim_x}, {props.max_block_dim_y}, {props.max_block_dim_z})",
)
print_property(
"Max dimension size of a grid size (x,y,z):",
f"({props.max_grid_dim_x}, {props.max_grid_dim_y}, {props.max_grid_dim_z})",
)
print_property("Maximum memory pitch:", f"{props.max_pitch} bytes")
print_property("Texture alignment:", f"{props.texture_alignment} bytes")
print_property(
"Concurrent copy and kernel execution:",
f"{fmt_yes_no(props.gpu_overlap)} with "
f"{props.async_engine_count} copy engine(s)",
)
print_property("Run time limit on kernels:", fmt_yes_no(props.kernel_exec_timeout))
print_property("Integrated GPU sharing Host Memory:", fmt_yes_no(props.integrated))
print_property(
"Support host page-locked memory mapping:",
fmt_yes_no(props.can_map_host_memory),
)
print_property(
"Device has ECC support:", "Enabled" if props.ecc_enabled else "Disabled"
)
if platform.system() == "Windows":
mode = (
"TCC (Tesla Compute Cluster Driver)"
if props.tcc_driver
else "WDDM (Windows Display Driver Model)"
)
print_property("CUDA Device Driver Mode (TCC or WDDM):", mode)
print_property(
"Device supports Unified Addressing (UVA):",
fmt_yes_no(props.unified_addressing),
)
print_property("Device supports Managed Memory:", fmt_yes_no(props.managed_memory))
print_property(
"Device supports Compute Preemption:",
fmt_yes_no(props.compute_preemption_supported),
)
print_property(
"Supports Cooperative Kernel Launch:", fmt_yes_no(props.cooperative_launch)
)
print_property(
"Device PCI Domain ID / Bus ID / location ID:",
f"{props.pci_domain_id} / {props.pci_bus_id} / {props.pci_device_id}",
)
compute_modes = {
0: (
"Default (multiple host threads can use cudaSetDevice() "
"with device simultaneously)"
),
1: (
"Exclusive (only one host thread in one process is able to "
"use cudaSetDevice() with this device)"
),
2: "Prohibited (no host thread can use cudaSetDevice() with this device)",
3: (
"Exclusive Process (many threads in one process is able to "
"use cudaSetDevice() with this device)"
),
}
print_property("Compute Mode:", "")
print(f" < {compute_modes.get(props.compute_mode, 'Unknown')} >")
def print_p2p_access_info(devices):
"""
Print peer-to-peer access information for multi-GPU systems.
Parameters
----------
devices : tuple of Device
Tuple of CUDA device objects
"""
print()
print("Peer-to-Peer (P2P) access support:")
for i, dev_i in enumerate(devices):
for j, dev_j in enumerate(devices):
if i == j:
continue
try:
can_access = dev_i.can_access_peer(dev_j)
print(
f"> Peer access from {dev_i.name} (GPU{i}) -> "
f"{dev_j.name} (GPU{j}) : {fmt_yes_no(can_access)}"
)
except Exception as e:
print(
"Warning: Could not check peer access between "
f"device {i} and {j}: {e}"
)
def query_devices(show_p2p=True):
"""
Query and display information about all CUDA devices.
Parameters
----------
show_p2p : bool
Whether to show peer-to-peer access information (default: True)
Returns
-------
bool
True if successful, False otherwise
"""
try:
print("[CUDA Device Query using CUDA Core API]")
devices = Device.get_all_devices()
except Exception as e:
print(f"Error: Failed to get devices: {e}")
import traceback
traceback.print_exc()
return False
if len(devices) == 0:
print("There are no available device(s) that support CUDA")
return True
print(f"Detected {len(devices)} CUDA Capable device(s)")
for dev_id, device in enumerate(devices):
try:
print_device_info(dev_id, device)
except Exception as e:
print(f"Error: Failed to get information for device {dev_id}: {e}")
import traceback
traceback.print_exc()
return False
if show_p2p and len(devices) >= 2:
print_p2p_access_info(devices)
return True
def main():
"""
Main entry point for the device query sample.
"""
import argparse
parser = argparse.ArgumentParser(
description="Query CUDA Device Properties using CUDA Core API",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--no-p2p", action="store_true", help="Skip peer-to-peer access information"
)
args = parser.parse_args()
success = query_devices(show_p2p=not args.no_p2p)
if success:
print("\nDone")
return 0
else:
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,4 @@
# Device Query Sample Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0

View File

@@ -0,0 +1,72 @@
# Sample: Kernel Nsys Profiling - CUDA C++ Kernel Profiling with cuda.core (Python)
## Description
This sample demonstrates how to profile custom CUDA C++ kernels compiled and launched with `cuda.core` using NVIDIA Nsight Systems. It implements three GPU operations (vector addition, SAXPY, vector transform) as custom kernels and shows how to instrument code with NVTX markers for profiling analysis.
## What you will learn
- How to write and compile CUDA C++ kernels with `cuda.core.Program`
- How to launch kernels with `LaunchConfig` and manage CUDA streams
- How to use NVTX markers (`nvtx.annotate()`) to annotate code sections
- How to profile kernels with Nsight Systems and analyze performance
- Modern CUDA Python workflow with `cuda.core.Device` and proper resource cleanup
## Requirements
- NVIDIA GPU with Compute Capability 7.0+
- CUDA Toolkit 13.0+
- Python 3.10+
- Packages: `numpy`, `cuda-python`, `cuda-core`, `cupy-cuda13x`, `nvtx` (see `requirements.txt`; NumPy >=2.3.2)
**Install:**
```bash
pip install -r requirements.txt
```
## How to run
```bash
python kernelNsysProfile.py
python kernelNsysProfile.py --array-size 10000000 # Custom size
```
## Nsys Profiling
**Basic profile:**
```bash
nsys profile -o gpu_profile python kernelNsysProfile.py
nsys-ui gpu_profile.nsys-rep # View results
```
The program uses color-coded NVTX markers:
- **Purple**: Phase 2 (cuda.core Custom Kernels - main focus)
- **Yellow/Blue/Green**: Other phases
- **Cyan**: Nested operations
Focus on Phase 2 to analyze kernel execution times, launch overhead, and GPU utilization.
**For detailed Nsys usage and analysis techniques, see the [NVIDIA Nsight Systems documentation](https://docs.nvidia.com/nsight-systems/).**
## Troubleshooting
**Missing packages:**
```bash
pip install -r requirements.txt
```
**Out of memory:**
```bash
python kernelNsysProfile.py -n 10000000 # Reduce array size
```
**Nsys not found:**
```bash
export PATH=/usr/local/cuda/bin:$PATH
```
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [NVIDIA Nsight Systems Documentation](https://docs.nvidia.com/nsight-systems/)
- [CuPy Documentation](https://docs.cupy.dev/)

View File

@@ -0,0 +1,327 @@
# 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
# distribution 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.
"""
Kernel Nsys Profiling Sample - CUDA C++ Kernel Profiling with cuda.core
This sample demonstrates how to profile custom CUDA C++ kernels compiled and
launched with cuda.core using NVIDIA Nsight Systems.
The sample implements three common GPU operations as custom CUDA C++ kernels:
- Vector addition: c = a + b
- SAXPY: y = alpha * x + y
- Vector transform: sqrt(x*x + 1) + sin(x)
Use Nsight Systems to analyze:
- Custom kernel execution times
- Kernel launch patterns and overhead
- GPU utilization and memory access patterns
- NVTX markers for structured profiling
Workflow:
- Phase 1: Create GPU arrays
- Phase 2: Compile and execute cuda.core custom kernels (profiling focus)
- Phase 3: Verify correctness with CuPy reference implementation
- Phase 4: Validate results
"""
import argparse
import sys
from pathlib import Path
try:
import cupy as cp
import numpy as np
import nvtx
from cuda.core import Device, LaunchConfig, launch
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
# Add parent directory to path to import utilities
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
from cuda_samples_utils import verify_array_result # noqa: E402
# CUDA C++ kernel definitions
# For larger projects, separating kernels into a separate file is also valid.
KERNELS_CODE = """
template<typename T>
__global__ void vector_add(const T* a, const T* b, T* c, size_t N) {
const unsigned int tid = threadIdx.x + blockIdx.x * blockDim.x;
for (size_t i = tid; i < N; i += gridDim.x * blockDim.x) {
c[i] = a[i] + b[i];
}
}
template<typename T>
__global__ void saxpy(const T alpha, const T* x, T* y, size_t N) {
const unsigned int tid = threadIdx.x + blockIdx.x * blockDim.x;
for (size_t i = tid; i < N; i += gridDim.x * blockDim.x) {
y[i] = alpha * x[i] + y[i];
}
}
template<typename T>
__global__ void vector_transform(const T* a, T* b, size_t N) {
const unsigned int tid = threadIdx.x + blockIdx.x * blockDim.x;
for (size_t i = tid; i < N; i += gridDim.x * blockDim.x) {
T val = a[i];
b[i] = sqrt(val * val + T(1.0)) + sin(val);
}
}
"""
def get_cuda_core_kernels(device):
"""
Compile cuda.core kernels and return them.
Args:
device: cuda.core.Device object
Returns:
dict: Dictionary of compiled kernels
"""
from cuda.core import Program, ProgramOptions
# Compile all kernels at once
program_options = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
prog = Program(KERNELS_CODE, code_type="c++", options=program_options)
mod = prog.compile(
"cubin",
name_expressions=(
"vector_add<float>",
"saxpy<float>",
"vector_transform<float>",
),
)
# Extract individual kernels
return {
"vector_add": mod.get_kernel("vector_add<float>"),
"saxpy": mod.get_kernel("saxpy<float>"),
"vector_transform": mod.get_kernel("vector_transform<float>"),
}
def run(size):
"""Main execution function"""
# =================================================================
# Device Initialization using cuda.core
# =================================================================
with nvtx.annotate("Device Initialization", color="green"):
try:
# Create device object (defaults to device 0)
dev = Device()
dev.set_current()
print()
print(f"Device: {dev.name}")
print(f"Compute Capability: sm_{dev.arch}")
print()
# Synchronize device
dev.sync()
except Exception as e:
print("ERROR: CUDA initialization failed!")
print(f"Error: {e}")
sys.exit(1)
print("Profiling cuda.core Custom Kernels")
print(f"Array size: {size:,}\n")
# Constant for SAXPY operation
alpha = 2.5
# Initialize random seed
rng = cp.random.default_rng(42)
# =================================================================
# Phase 1: Create GPU Arrays with CuPy
# =================================================================
with nvtx.annotate("Create GPU Arrays", color="yellow"):
a_gpu = rng.standard_normal(size, dtype=cp.float32)
b_gpu = rng.standard_normal(size, dtype=cp.float32)
dev.sync()
print("Phase 1: Created arrays on GPU with CuPy")
print(f" Array shape: {a_gpu.shape}")
print(f" Array dtype: {a_gpu.dtype}")
print(
f" Array a - Mean: {float(cp.mean(a_gpu)):.4f}, "
f"Std: {float(cp.std(a_gpu)):.4f}"
)
print(
f" Array b - Mean: {float(cp.mean(b_gpu)):.4f}, "
f"Std: {float(cp.std(b_gpu)):.4f}\n"
)
# =================================================================
# Phase 2: cuda.core Custom Kernels on GPU
# =================================================================
with nvtx.annotate("cuda.core Custom Kernels", color="purple"):
print("Phase 2: cuda.core custom CUDA C++ kernels on GPU")
# Create a stream for cuda.core operations
stream = dev.create_stream()
try:
with nvtx.annotate("Compile Kernels", color="cyan"):
kernels_dict = get_cuda_core_kernels(dev)
stream.sync()
print("Compiled custom CUDA C++ kernels")
# Prepare launch configuration
# Grid-stride loops in kernels handle any grid size robustly
block = 256
grid = (size + block - 1) // block
config = LaunchConfig(grid=grid, block=block)
# Execute cuda.core vector_add kernel
with nvtx.annotate("Vector Add (cuda.core)", color="cyan"):
c_cuda = cp.empty_like(a_gpu)
launch(
stream,
config,
kernels_dict["vector_add"],
a_gpu.data.ptr,
b_gpu.data.ptr,
c_cuda.data.ptr,
cp.uint64(size),
)
stream.sync()
# Execute cuda.core SAXPY kernel
with nvtx.annotate("SAXPY (cuda.core)", color="cyan"):
y_cuda = b_gpu.copy()
launch(
stream,
config,
kernels_dict["saxpy"],
np.float32(alpha),
a_gpu.data.ptr,
y_cuda.data.ptr,
cp.uint64(size),
)
stream.sync()
# Execute cuda.core vector_transform kernel
with nvtx.annotate("Vector Transform (cuda.core)", color="cyan"):
transform_cuda = cp.empty_like(a_gpu)
launch(
stream,
config,
kernels_dict["vector_transform"],
a_gpu.data.ptr,
transform_cuda.data.ptr,
cp.uint64(size),
)
stream.sync()
print("Vector Addition (custom kernel)")
print("SAXPY (custom kernel)")
print("Vector Transform (custom kernel)\n")
finally:
stream.close()
# =================================================================
# Phase 3: Generate Reference Results with CuPy (for verification)
# =================================================================
with nvtx.annotate("Generate Reference Results", color="blue"):
print("Phase 3: Generate reference results for verification")
with nvtx.annotate("Vector Add (Reference)", color="cyan"):
c_cupy = a_gpu + b_gpu
dev.sync()
with nvtx.annotate("SAXPY (Reference)", color="cyan"):
y_cupy = alpha * a_gpu + b_gpu
dev.sync()
with nvtx.annotate("Vector Transform (Reference)", color="cyan"):
transform_cupy = cp.sqrt(a_gpu * a_gpu + 1.0) + cp.sin(a_gpu)
dev.sync()
print("Reference results generated\n")
# =================================================================
# Phase 4: Verify Kernel Correctness
# =================================================================
with nvtx.annotate("Verification", color="green"):
print("Phase 4: Verify kernel correctness")
# Verify custom kernels against reference results
# Use relaxed tolerances for single-precision float comparisons
# Small differences can occur due to instruction ordering and
# compiler optimizations
print(" Validating cuda.core kernels:")
print(" Vector Add: ", end="")
vec_add_match = verify_array_result(c_cuda, c_cupy, rtol=1e-5, atol=1e-6)
print(" SAXPY: ", end="")
saxpy_match = verify_array_result(y_cuda, y_cupy, rtol=1e-5, atol=1e-6)
print(" Transform: ", end="")
transform_match = verify_array_result(
transform_cuda, transform_cupy, rtol=1e-5, atol=1e-6
)
all_pass = vec_add_match and saxpy_match and transform_match
if not all_pass:
print("\n ERROR: Kernel verification failed!")
return 1
print()
# Final synchronization
dev.sync()
print("The sample is complete PASSED!")
def main():
parser = argparse.ArgumentParser(
description="Kernel Nsys Profiling - Profile custom CUDA C++ "
"kernels with cuda.core"
)
parser.add_argument(
"-n",
"--array-size",
type=int,
default=50000,
metavar="N",
help="Array size (default: 50,000)",
)
args = parser.parse_args()
run(size=args.array_size)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,7 @@
# Nsight System Kernels Profiling Sample - Requirements
numpy>=2.3.2
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0
nvtx

View File

@@ -0,0 +1,73 @@
# Sample: Numpy vs. Cupy (Python)
## Description
This sample demonstrates performance comparison between NumPy (CPU) and CuPy (GPU) for matrix multiplication operations. It benchmarks the execution time of matrix dot products on both CPU and GPU, showing the performance benefits of GPU acceleration for numerical computations.
## What you will learn
- How to set up and use CuPy for GPU-accelerated numerical computations.
- How to benchmark NumPy vs CuPy performance for matrix operations.
- How to transfer data between CPU (NumPy) and GPU (CuPy) memory using `cp.asarray()`.
- How to use CUDA device management with the cuda-core library.
- How to validate computational results between CPU and GPU implementations using `np.testing.assert_allclose()`.
- How to handle GPU warmup to avoid first-run overhead in benchmarking.
- How to create and manage explicit CUDA streams with `device.create_stream()`.
- How to properly cleanup streams with `stream.close()` in try/finally blocks.
- How to access GPU device information (name, compute capability).
- How to create timing context managers for performance measurement using CUDA events.
## Key libraries
- `numpy`
- `cupy`
- `cuda-core`
## Key APIs
**From cuda.core:**
- `Device()` – Get CUDA device object for specific GPU
- `device.create_stream()` – Create explicit CUDA stream
- `stream.close()` – Close and cleanup stream resources
## Requirements
1. **NVIDIA Graphics Card** with CUDA support
2. **CUDA Drivers** installed on your system
3. **CUDA Toolkit** installed on your system
4. **Python 3.12 or newer**
**Install packages:**
```bash
pip install -r requirements.txt
```
## How to run
Basic usage:
```bash
# Pre-steps:
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
# Run from the Python directory:
cd /path/to/numpyVsCupy/Python
python -m 1_GettingStarted.numpyVsCupy.numpyVsCupy
```
With custom parameters:
```bash
python -m 1_GettingStarted.numpyVsCupy.numpyVsCupy --n_size 5000
```
### Command line arguments
- `--n_size`, `-n`: Size of the matrix (n * n) for benchmarking (default: 4096)
## Expected Output
```
Validation PASSED: NumPy and CuPy results match within tolerance
Demo completed successfully!
```
## Files
- `numpyVsCupy.py` – Python implementation
- `README.md` – This file
- `requirements.txt` – Required packages

View File

@@ -0,0 +1,141 @@
# 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
# distribution 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.
import argparse
import contextlib
import sys
import time
from pathlib import Path
try:
import cupy as cp
import numpy as np
from cuda.core import Device, EventOptions
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
# Add parent directory to path to import utilities
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
from cuda_samples_utils import verify_array_result
@contextlib.contextmanager
def timer(message):
"""CPU timing context manager."""
start = time.time()
yield
end = time.time()
print(f"{message}: {(end - start):.6f} seconds")
@contextlib.contextmanager
def gpu_timer(message, stream):
"""GPU timing context manager using cuda.core CUDA events."""
event_options = EventOptions(enable_timing=True)
start_event = stream.record(options=event_options)
yield
end_event = stream.record(options=event_options)
end_event.sync()
elapsed_time_ms = end_event - start_event # Returns milliseconds
elapsed_time_s = elapsed_time_ms / 1000.0 # Convert to seconds
print(f"{message}: {elapsed_time_s:.6f} seconds")
def warmup():
# Pre-runs a simple GPU operation to avoid first-run overhead in benchmarking.
print("Warmup...")
a_cp = cp.ones((16, 16))
b_cp = cp.ones((16, 16))
result_cp = cp.dot(a_cp, b_cp)
return result_cp
def run(n):
# Benchmarks NumPy vs. CuPy matrix multiplication for n x n random arrays.
# Prints timing results.
device = Device() # Use device 0 explicitly
device.set_current()
major, minor = device.compute_capability
print()
print(f"Device Name: {device.name}, SM: {major}.{minor}")
print()
# Create explicit stream
stream = device.create_stream()
try:
# Warm up GPU before measuring
warmup()
stream.sync()
# Generate random matrices on CPU
a_np = np.random.rand(n, n)
b_np = np.random.rand(n, n)
# NumPy dot product (CPU)
with timer(f"NumPy dot of {n}*{n} arrays"):
result_np = np.dot(a_np, b_np)
# Transfer NumPy arrays to GPU (using events for timing)
with gpu_timer("Transfer arrays to GPU", stream):
a_cp = cp.asarray(a_np)
b_cp = cp.asarray(b_np)
# CuPy dot product (GPU) - using events for accurate GPU timing
with gpu_timer(f"CuPy dot of {n}*{n} arrays", stream):
result_cp = cp.dot(a_cp, b_cp)
print()
# Result validation
if not verify_array_result(result_np, result_cp.get()):
print(
"Validation FAILED: NumPy and CuPy results do not match "
"within tolerance"
)
sys.exit(1)
print("Validation PASSED: NumPy and CuPy results match within tolerance")
finally:
stream.close()
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"--n_size", "-n", default=4096, type=int, help="Size of the matrix(n * n)."
)
args = parser.parse_args()
run(args.n_size)
print("Demo completed successfully!")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,7 @@
# Numpy vs. Cupy - Requirements
# Install with: pip install -r requirements.txt
numpy>=2.3.2
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0

View File

@@ -0,0 +1,263 @@
# simplePrint - Printing from CUDA Kernels
## Description
This sample demonstrates how to use `printf()` inside CUDA kernels using **two different approaches**:
1. **CUDA C++ kernels** compiled with `cuda.core.Program` - Full C++ features and control
2. **Numba CUDA kernels** - Pythonic kernel authoring using `numba.cuda.grid()` for modern indexing
The sample shows basic device management, kernel compilation with inline CUDA C++ code, and multi-dimensional kernel launches (2D grid × 3D blocks) using modern CUDA Python. The Numba example demonstrates the recommended `numba.cuda.grid()` indexing style while also showing how it relates to classic CUDA C++ block/thread IDs. Both approaches use `cuda.core` APIs for stream management and synchronization, demonstrating interoperability.
This is the Python equivalent of the C++ `simplePrintf` sample, enhanced with Numba CUDA examples.
## Key Concepts
CUDA Python (cuda.core), Numba CUDA, Kernel Compilation, Printf in Kernels, Multi-dimensional Launch, Pythonic GPU Programming, Modern Thread Indexing (grid()), Stream-based Execution, cuda.core/Numba Interoperability
## CUDA APIs involved
### [cuda.core (cuda-python)](https://nvidia.github.io/cuda-python/)
- `Device()` - Device management
- `Device.create_stream()` - Create CUDA streams
- `Stream.sync()` - Synchronize stream execution
- `Program()` - Compile CUDA C++ kernels
- `LaunchConfig()` - Configure kernel launch
- `launch()` - Execute kernels on streams
### [Numba CUDA](https://nvidia.github.io/numba-cuda/)
- `@cuda.jit` - JIT compile Python functions to CUDA kernels
- `cuda.grid()` - Get global thread position (recommended modern approach)
- `cuda.blockIdx`, `cuda.threadIdx` - Thread/block indices (classic style)
- `cuda.gridDim`, `cuda.blockDim` - Grid/block dimensions
- **Note:** Uses `cuda.core` APIs for stream management (interoperability)
### CUDA Kernel Functions
- `printf()` - Print from device code (C++)
- `print()` - Print from device code (Numba, limited formatting)
- `blockIdx`, `threadIdx` - Thread/block indices
- `gridDim`, `blockDim` - Grid/block dimensions
### What You Learn
- Device initialization with `cuda.core.Device`
- Compiling CUDA C++ kernels with `Program` and `ProgramOptions`
- Writing Pythonic CUDA kernels with Numba's `@cuda.jit` decorator
- Using `numba.cuda.grid()` for modern thread indexing (recommended approach)
- Understanding the relationship between global coordinates and classic block/thread IDs
- **Interoperability**: Using `cuda.core` streams with Numba CUDA kernels
- Comparing CUDA C++ vs Pythonic kernel authoring approaches
- Multi-dimensional kernel launches (2D grid, 3D blocks)
- Using streams for kernel execution and synchronization
- Using `printf()` and `print()` in GPU kernels for debugging
- Understanding print limitations in Numba CUDA (no f-strings)
- Proper error handling and resource management
## Requirements
### Hardware:
- NVIDIA GPU with Compute Capability 7.0 or higher
- Minimum GPU memory: 512 MB
### Software:
- CUDA Toolkit 13.0 or newer
- Python 3.10 or newer
- `cuda-python` package (13.0+)
- `cuda-core` package (>=0.6.0)
- `numba-cuda` package (0.24.0+, for Pythonic kernel authoring)
Download and install:
- [CUDA Toolkit](https://developer.nvidia.com/cuda-downloads)
- [cuda-python package](https://nvidia.github.io/cuda-python/): `pip install cuda-python`
- [numba-cuda](https://nvidia.github.io/numba-cuda/): `pip install numba-cuda`
## Build and Run
```bash
# Install dependencies
pip install -r requirements.txt
# Run the sample
python simplePrint.py
```
## Expected Output
```
Simple Print - Printing from CUDA Kernels
Demonstrating both CUDA C++ and Numba CUDA approaches
Device: <Your GPU Name>
Compute Capability: sm_<XX>
======================================================================
METHOD 1: CUDA C++ Kernel (via cuda.core.Program)
======================================================================
Advantage: Full C++ features, better for complex kernels
Compiling CUDA C++ kernel...
Kernel compiled successfully.
Kernel configuration:
Grid: (2, 2)
Block: (2, 2, 2)
Total threads: 32
Launching kernel with value=10. Output:
[0, 0]: Value is: 10
[0, 1]: Value is: 10
[0, 2]: Value is: 10
[0, 3]: Value is: 10
[0, 4]: Value is: 10
[0, 5]: Value is: 10
[0, 6]: Value is: 10
[0, 7]: Value is: 10
[1, 0]: Value is: 10
...
[3, 7]: Value is: 10
CUDA C++ kernel execution complete.
======================================================================
METHOD 2: Numba CUDA Kernel (Pythonic / modern indexing)
======================================================================
Advantage: Uses numba.cuda.grid(3) for global indexing,
while still showing classic CUDA C++ IDs for reference.
Uses cuda.core for stream management (interoperability).
Kernel configuration:
Grid: (2, 2)
Block: (2, 2, 2)
Total threads: 32
Launching Numba CUDA kernel (grid(3) + classic IDs) with value=10:
Uses numba.cuda.grid(3) to get global (x, y, z),
and prints the corresponding blockId/threadId like the C++ sample.
Stream managed by cuda.core for consistency with C++ example.
global[ 0 , 0 , 0 ] -> [ 0 , 0 ]: Value is: 10
global[ 1 , 0 , 0 ] -> [ 0 , 1 ]: Value is: 10
global[ 0 , 1 , 0 ] -> [ 0 , 2 ]: Value is: 10
...
global[ 3 , 3 , 1 ] -> [ 3 , 7 ]: Value is: 10
Numba CUDA kernel execution complete.
======================================================================
Done! Both kernel approaches demonstrated successfully.
======================================================================
```
## Understanding the Output
- **Grid**: 2×2 = 4 blocks (labeled 0-3)
- **Block**: 2×2×2 = 8 threads per block (labeled 0-7)
- **Total**: 32 threads, each printing its position and value
### CUDA C++ Kernel:
Each thread calculates:
- Block ID (linear): `blockIdx.y * gridDim.x + blockIdx.x`
- Thread ID (linear): `threadIdx.z * blockDim.x * blockDim.y + threadIdx.y * blockDim.x + threadIdx.x`
### Numba CUDA Kernel:
Each thread shows:
- **Global position** using `numba.cuda.grid(3)` → `(x, y, z)` coordinates across entire grid
- **Classic IDs** (block ID, thread ID) calculated the same way as C++ for comparison
- This demonstrates how modern indexing relates to traditional CUDA C++ style
### Comparing the Two Approaches
**CUDA C++ Kernel (Method 1):**
- Uses C++ syntax and `printf()` with full formatting control
- Requires compilation via `cuda.core.Program`
- Best for complex kernels needing C++ features (templates, libraries, etc.)
- Uses classic block/thread ID indexing
- Output: `[0, 0]: Value is: 10` (clean formatting)
**Numba CUDA Kernel (Method 2):**
- Uses Python syntax with `@cuda.jit` decorator
- JIT compiled automatically when called
- Best for prototyping and simpler kernels
- **Modern indexing**: Uses `numba.cuda.grid(3)` to get global thread coordinates (recommended)
- Also shows classic block/thread IDs to help relate the two indexing models
- **Interoperability**: Uses `cuda.core` streams via `stream` for consistency
- Demonstrates that numba-cuda kernels can work seamlessly with cuda.core infrastructure
- Limited print formatting (no f-strings, basic `print()` only; adds spaces between arguments)
- Output: `global[ 0 , 0 , 0 ] -> [ 0 , 0 ]: Value is: 10` (shows both indexing styles; note extra spaces due to `print()` behavior)
## Experiments
Try modifying:
### For Both Approaches:
- **Grid size**: Change `grid=(4, 4)` for 16 blocks
- **Block size**: Change `block=(4, 4, 4)` for 64 threads per block
- **Conditional printing**: Print only from specific threads (e.g., `if threadId == 0:`)
### CUDA C++ Specific:
- **Format strings**: Experiment with different `printf()` formats
- **Kernel code**: Add complex C++ computations before printing
- **External libraries**: Include CUDA math libraries or device functions (e.g., `<cuda/std/cmath>`, `<cub/cub.cuh>`)
### Numba CUDA Specific:
- **Grid indexing**: Try `numba.cuda.grid(1)` or `numba.cuda.grid(2)` for different dimensions
- **Conditional printing**: Print only from threads where `x == 0` or `y == z`
- **Python operations**: Use NumPy-like operations in the kernel
- **Device math libraries**: Use [nvmath-python device APIs](https://docs.nvidia.com/cuda/nvmath-python/latest/device-apis/index.html) for optimized math operations (similar to CUDA math libraries in C++)
- **Shared memory**: Add `numba.cuda.shared.array()` for fast inter-thread communication
- **Atomic operations**: Try `numba.cuda.atomic.add()` for thread-safe updates
- **Print variations**: Experiment with what numba-cuda's `print()` can and cannot handle
- **Streams**: Create multiple `cuda.core` streams and launch numba-cuda kernels on them concurrently
- **Interoperability**: Mix numba-cuda kernels and CUDA C++ kernels on the same stream
## Notes
### General:
- Printing from GPU is relatively slow - use sparingly in production code
- Printf output is buffered and limited (~1MB buffer on most GPUs)
### CUDA C++ Kernels:
- Always call `stream.sync()` after kernel launch to flush printf output
- Full `printf()` format string support (%, flags, width, precision)
### Numba CUDA Kernels:
- **Recommended**: Use `numba.cuda.grid(ndim)` for thread indexing (modern, Pythonic)
- `grid(1)` for 1D indexing, `grid(2)` for 2D, `grid(3)` for 3D
- Returns global thread position across the entire grid
- **Interoperability**: Use `cuda.core` streams with Numba kernels via `stream`
- Create streams: `stream = device.create_stream()`
- Launch kernels: `kernel[grid, block, stream](args)`
- Synchronize: `stream.sync()`
- Numba's `print()` has limited capabilities compared to Python's `print()`
- F-strings are NOT supported in Numba CUDA kernels
- Use comma-separated arguments: `print("Value:", x)` instead of f-strings
- **Note**: `print()` automatically adds spaces between comma-separated arguments (e.g., `print("[", x, "]")` outputs `[ 0 ]` not `[0]`)
- Always synchronize the stream to flush output
## Files
- `simplePrint.py` - Python implementation using cuda.core API
- `README.md` - This file
- `requirements.txt` - Sample dependencies
## See Also
### CUDA Python (cuda.core):
- [cuda.core Documentation](https://nvidia.github.io/cuda-python/)
- [CUDA Python Examples](https://github.com/NVIDIA/cuda-python/tree/main/cuda_core/examples)
### Numba CUDA:
- [Numba CUDA Documentation](https://nvidia.github.io/numba-cuda/)
- [numba.cuda.grid() Reference](https://nvidia.github.io/numba-cuda/reference/kernel.html#numba.cuda.grid)
- [nvmath-python Device APIs](https://docs.nvidia.com/cuda/nvmath-python/latest/device-apis/index.html) - Optimized math operations for Numba CUDA kernels
### CUDA References:
- [CUDA C Programming Guide - Printf](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#formatted-output)
- [C++ simplePrintf Sample](https://github.com/NVIDIA/cuda-samples/tree/master/Samples/0_Introduction/simplePrintf)

View File

@@ -0,0 +1,7 @@
# Simple Printf Sample - Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0
# Numba JIT uses nvJitLink from pip; keep in step with cuda-bindings (e.g. 13.2.x).
nvidia-nvjitlink>=13.2.0
numba-cuda>=0.24.0

View File

@@ -0,0 +1,287 @@
# 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.
"""
Simple Print - Printing from CUDA Kernels
This sample demonstrates how to print output from CUDA kernels using printf().
It shows:
1. Device management with cuda.core.Device
2. Compiling CUDA C++ code that uses printf()
3. Launching kernels with 2D grids and 3D blocks
4. Seeing kernel output printed to stdout
5. Using Numba CUDA for Pythonic kernel authoring
This sample demonstrates both approaches:
- CUDA C++ kernels compiled via cuda.core.Program (more control, C++ features)
- Numba CUDA kernels (more Pythonic, easier to write)
This is the Python equivalent of the C++ simplePrintf sample.
"""
import sys
import traceback
try:
from cuda.core import Device, LaunchConfig, Program, ProgramOptions, launch
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
try:
from numba import cuda as numba_cuda
NUMBA_AVAILABLE = True
except ImportError:
NUMBA_AVAILABLE = False
print("Warning: numba not found. Numba CUDA example will be skipped.")
print("To install: pip install numba")
# CUDA C++ kernel with printf
# This kernel prints the block index, thread index, and a value from each thread
PRINTF_KERNEL = """
extern "C"
__global__ void printKernel(int val) {
// Calculate linear block index from 2D grid
int blockId = blockIdx.y * gridDim.x + blockIdx.x;
// Calculate linear thread index from 3D block
int threadId = threadIdx.z * blockDim.x * blockDim.y +
threadIdx.y * blockDim.x +
threadIdx.x;
// Print from each thread
printf("[%d, %d]:\\t\\tValue is: %d\\n", blockId, threadId, val);
}
"""
# Numba CUDA kernel - Pythonic equivalent using numba.cuda.grid()
# This demonstrates the same functionality using Numba's Python-based kernel syntax
if NUMBA_AVAILABLE:
@numba_cuda.jit
def numba_print_kernel(val):
"""
Numba CUDA kernel showing the *recommended* grid() indexing style,
while also relating it to the classic CUDA C++ blockId/threadId.
- Primary view: global 3D coordinates from numba.cuda.grid(3)
(modern, Pythonic way to index work for a 3D thread layout).
- Secondary view: linear blockId / threadId matching the CUDA C++
printf sample, to help CUDA C++ users connect the two models.
"""
# Modern / recommended view: global 3D thread coordinates
x, y, z = numba_cuda.grid(3)
# Classic CUDA-style indices, same formulas as the C++ sample
block_id = numba_cuda.blockIdx.y * numba_cuda.gridDim.x + numba_cuda.blockIdx.x
thread_id = (
numba_cuda.threadIdx.z * numba_cuda.blockDim.x * numba_cuda.blockDim.y
+ numba_cuda.threadIdx.y * numba_cuda.blockDim.x
+ numba_cuda.threadIdx.x
)
# Print both views side-by-side
# Note: Numba print() adds spaces between comma-separated args
print(
"global[",
x,
",",
y,
",",
z,
"] -> [",
block_id,
",",
thread_id,
"]:\t\tValue is:",
val,
)
def run_cuda_cpp_kernel(device, test_value=10):
"""
Demonstrate printing from CUDA C++ kernel compiled with cuda.core.
This approach gives you full access to CUDA C++ features and allows
for more complex kernel implementations.
"""
print("=" * 70)
print("METHOD 1: CUDA C++ Kernel (via cuda.core.Program)")
print("=" * 70)
print("Advantage: Full C++ features, better for complex kernels")
print()
# Compile the kernel
print("Compiling CUDA C++ kernel...")
program_options = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
prog = Program(PRINTF_KERNEL, code_type="c++", options=program_options)
mod = prog.compile("cubin", name_expressions=("printKernel",))
kernel = mod.get_kernel("printKernel")
print("Kernel compiled successfully.\n")
# Create stream for kernel execution
stream = device.create_stream()
# Configure kernel launch
# Using 2D grid (2x2) and 3D blocks (2x2x2)
grid_x, grid_y = 2, 2
block_x, block_y, block_z = 2, 2, 2
print("Kernel configuration:")
print(f" Grid: ({grid_x}, {grid_y})")
print(f" Block: ({block_x}, {block_y}, {block_z})")
print(f" Total threads: {grid_x * grid_y * block_x * block_y * block_z}")
print()
# Launch configuration with 2D grid and 3D block
config = LaunchConfig(grid=(grid_x, grid_y), block=(block_x, block_y, block_z))
print(f"Launching kernel with value={test_value}. Output:\n")
try:
# Launch kernel
launch(stream, config, kernel, test_value)
# Synchronize to ensure printf output is flushed
stream.sync()
print("\nCUDA C++ kernel execution complete.")
except Exception as e:
print(f"\nError during kernel execution: {e}")
traceback.print_exc()
return 1
finally:
# Cleanup
stream.close()
return 0
def run_numba_kernel(device, test_value=10):
"""
Demonstrate printing from a Numba CUDA kernel.
This example uses numba.cuda.grid(3) as the primary indexing mechanism
(recommended modern style), and also prints the equivalent blockId /
threadId used in the CUDA C++ printf sample for side-by-side comparison.
Uses cuda.core APIs for stream management, demonstrating interoperability
between Numba CUDA kernels and cuda.core infrastructure.
"""
print("\n")
print("=" * 70)
print("METHOD 2: Numba CUDA Kernel (Pythonic / modern indexing)")
print("=" * 70)
print("Advantage: Uses numba.cuda.grid(3) for global indexing,")
print(" while still showing classic CUDA C++ IDs for reference.")
print(" Uses cuda.core for stream management (interoperability).")
print()
# Same launch configuration as the C++ version
grid_x, grid_y = 2, 2
block_x, block_y, block_z = 2, 2, 2
print("Kernel configuration:")
print(f" Grid: ({grid_x}, {grid_y})")
print(f" Block: ({block_x}, {block_y}, {block_z})")
print(f" Total threads: {grid_x * grid_y * block_x * block_y * block_z}")
print()
# Use cuda.core stream (same as C++ example) instead of numba.cuda.stream()
stream = device.create_stream()
print(f"Launching Numba kernel (grid(3) + classic IDs) with value={test_value}:")
print("Uses numba.cuda.grid(3) to get global (x, y, z),")
print("and prints the corresponding blockId/threadId like the C++ sample.")
print("Stream managed by cuda.core for consistency with C++ example.\n")
try:
# Launch Numba kernel on cuda.core stream
numba_print_kernel[(grid_x, grid_y), (block_x, block_y, block_z), stream](
test_value
)
# Synchronize cuda.core stream (same as C++ example)
stream.sync()
print("\nNumba CUDA kernel execution complete.")
except Exception as e:
print(f"\nError during Numba kernel execution: {e}")
traceback.print_exc()
return 1
finally:
# Cleanup
stream.close()
return 0
def main():
"""Main function demonstrating printing from CUDA kernels using both approaches"""
print("Simple Print - Printing from CUDA Kernels")
print("Demonstrating both CUDA C++ and Numba CUDA approaches")
print()
# Initialize device
device = Device()
device.set_current()
# Get device properties
print(f"Device: {device.name}")
print(f"Compute Capability: sm_{device.arch}")
print()
# Value to pass to both kernels
test_value = 10
# Run CUDA C++ kernel
result = run_cuda_cpp_kernel(device, test_value)
if result != 0:
return result
# Run Numba kernel if available
if NUMBA_AVAILABLE:
result = run_numba_kernel(device, test_value)
if result != 0:
return result
else:
print("\n" + "=" * 70)
print("Numba CUDA example skipped (numba not installed)")
print("To run the Numba example: pip install numba")
print("=" * 70)
print("\n" + "=" * 70)
print("Done! Both kernel approaches demonstrated successfully.")
print("=" * 70)
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,152 @@
# Sample: System Information Query (Python)
## Description
This sample demonstrates how to inspect the CUDA driver, NVML, and every
installed GPU through the
[`cuda.core.system`](https://nvidia.github.io/cuda-python/cuda-core/latest/)
module.
`cuda.core.system` wraps the NVIDIA Management Library (NVML) and can be
imported without CUDA being installed or initialized, so it is useful as a
lightweight pre-flight check before any CUDA context is created. The script
prints driver and NVML versions, the current process name, per-device
metadata (name, compute capability, architecture, memory, PCI info,
temperature, performance state), and, on multi-GPU systems, the topology
and peer-to-peer capabilities between each pair of devices.
## What You'll Learn
- Querying CUDA driver and NVML versions with `cuda.core.system`
- Enumerating GPUs without creating a CUDA context
- Reading per-device metadata exposed by NVML (name, UUID, memory usage,
temperature, performance state)
- Inspecting GPU-to-GPU topology and peer-to-peer (P2P) capabilities
## Key Libraries
- [`cuda.core.system`](https://nvidia.github.io/cuda-python/cuda-core/latest/) - Python wrapper around NVML
## Key APIs
From `cuda.core.system`:
- `get_driver_version()`, `get_driver_version_full()`, `get_driver_branch()` - CUDA driver version tuple and branch string
- `get_nvml_version()` - NVML library version
- `get_num_devices()` - number of GPUs visible to NVML
- `get_process_name(pid)` - process name for a given PID
- `Device(index=...)` - NVML-backed device handle (no CUDA context required)
- `name`, `uuid`, `cuda_compute_capability`, `arch`, `brand`
- `memory_info` (`total`, `used`, `free`)
- `pci_info` (`domain`, `bus`, `device`, `bus_id`)
- `temperature.sensor(TemperatureSensors.TEMPERATURE_GPU)`
- `performance_state`
- `get_topology_common_ancestor(dev0, dev1)` - `GpuTopologyLevel` between two devices
- `get_p2p_status(dev0, dev1, GpuP2PCapsIndex.P2P_CAPS_INDEX_READ)` - peer-access capability between two devices
Import stable symbols from the top-level `cuda.core` package (not `cuda.core.experimental`).
## Requirements
1. **NVIDIA Graphics Card** with CUDA support
2. **CUDA Drivers** installed on your system
3. **CUDA Toolkit** installed on your system
4. **Python 3.12 or newer**
### Hardware
- One or more NVIDIA GPUs
- Driver compatible with `cuda-python` 13.x
### Software
- CUDA Toolkit 13.0 or newer (matches `cuda-python` 13.x)
- Python 3.10 or newer
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
## Installation
Install the required packages from `requirements.txt`:
```bash
cd /path/to/cuda-samples/python/1_GettingStarted/systemInfo
pip install -r requirements.txt
```
The `requirements.txt` installs:
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
## How to Run
### Basic usage
```bash
cd cuda-samples/python/1_GettingStarted/systemInfo
python systemInfo.py
```
### Skip topology queries
Useful on machines with only one GPU or to shorten the output:
```bash
python systemInfo.py --no-topology
```
## Expected Output
Output varies with your hardware. On a machine with two GPUs you should see
something like:
```
======================================================================
Driver / NVML
======================================================================
CUDA driver version: 13.2
CUDA driver version (full): (13, 2, 0)
NVML version: (13, 595, 58, 3)
Driver branch: r595_88
Current process: /usr/bin/python
======================================================================
Devices detected: 2
======================================================================
-- Device 0 --
Name: <Your GPU Name>
UUID: ...
Compute capability: 8.9
Architecture: ADA
Brand: BRAND_GEFORCE
Memory: total=23.99 GiB, used=960.00 KiB, free=23.52 GiB
PCI: domain=0000 bus=41 device=00 id=00000000:41:00.0
Temperature (GPU sensor): 47 C
Performance state: <Pstates.PSTATE_8: 8>
...
======================================================================
GPU topology and peer-to-peer
======================================================================
Device 0 <-> Device 1: topology=TOPOLOGY_HOSTBRIDGE, p2p_read=..., p2p_write=...
Done
```
**Note:** Device names, compute capability, temperatures, and topology
details will vary based on your GPUs and system.
## Files
- `systemInfo.py` - Python implementation using `cuda.core.system`
- `README.md` - This file
- `requirements.txt` - Sample dependencies
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [`cuda.core.system` API reference](https://nvidia.github.io/cuda-python/cuda-core/latest/api.html)
- [NVML reference](https://docs.nvidia.com/deploy/nvml-api/)

View File

@@ -0,0 +1,4 @@
# System Information Sample Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0

View File

@@ -0,0 +1,215 @@
# 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.
"""
System Information via cuda.core.system (NVML)
Demonstrates the ``cuda.core.system`` module, which wraps NVIDIA Management
Library (NVML) functionality.
This sample prints:
* Driver and NVML versions
* Current process name
* Per-device: name, UUID, compute capability / arch, PCI info, memory usage,
temperature, performance state
* GPU-to-GPU topology and peer-to-peer status (when more than one GPU)
"""
import os
import sys
try:
from cuda.core import system
from cuda.core.system import (
CUDA_BINDINGS_NVML_IS_COMPATIBLE,
GpuP2PCapsIndex,
TemperatureSensors,
)
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
def print_header(title: str) -> None:
print()
print("=" * 70)
print(title)
print("=" * 70)
def format_bytes(nbytes: int) -> str:
"""Format a byte count as a human-readable string."""
units = ["B", "KiB", "MiB", "GiB", "TiB"]
size = float(nbytes)
for unit in units:
if size < 1024.0:
return f"{size:.2f} {unit}"
size /= 1024.0
return f"{size:.2f} PiB"
def print_driver_info() -> None:
print_header("Driver / NVML")
major, minor = system.get_driver_version()
print(f"CUDA driver version: {major}.{minor}")
print(f"CUDA driver version (full): {system.get_driver_version_full()}")
if CUDA_BINDINGS_NVML_IS_COMPATIBLE:
print(f"NVML version: {system.get_nvml_version()}")
try:
print(f"Driver branch: {system.get_driver_branch()}")
except Exception as e: # noqa: BLE001 - driver branch is informational
print(f"Driver branch: unavailable ({e})")
else:
print(
"NVML bindings are not compatible with this driver; "
"device info will be limited."
)
print(f"Current process: {system.get_process_name(os.getpid())}")
def print_device_info(device: "system.Device") -> None:
print(f"\n-- Device {device.index} --")
print(f"Name: {device.name}")
print(f"UUID: {device.uuid}")
try:
cc_major, cc_minor = device.cuda_compute_capability
print(f"Compute capability: {cc_major}.{cc_minor}")
except Exception as e: # noqa: BLE001
print(f"Compute capability: unavailable ({e})")
try:
print(f"Architecture: {device.arch.name}")
except Exception as e: # noqa: BLE001
print(f"Architecture: unavailable ({e})")
try:
print(f"Brand: {device.brand.name}")
except Exception as e: # noqa: BLE001
print(f"Brand: unavailable ({e})")
# Memory
try:
mem = device.memory_info
print(
f"Memory: total={format_bytes(mem.total)}, "
f"used={format_bytes(mem.used)}, "
f"free={format_bytes(mem.free)}"
)
except Exception as e: # noqa: BLE001
print(f"Memory: unavailable ({e})")
# PCI
try:
pci = device.pci_info
print(
f"PCI: domain={pci.domain:04x} bus={pci.bus:02x} "
f"device={pci.device:02x} id={pci.bus_id}"
)
except Exception as e: # noqa: BLE001
print(f"PCI: unavailable ({e})")
# Temperature (GPU sensor)
try:
temp_c = device.temperature.sensor(TemperatureSensors.TEMPERATURE_GPU)
print(f"Temperature (GPU sensor): {temp_c} C")
except Exception as e: # noqa: BLE001
print(f"Temperature: unavailable ({e})")
# Performance state
try:
pstate = device.performance_state
print(f"Performance state: {pstate}")
except Exception as e: # noqa: BLE001
print(f"Performance state: unavailable ({e})")
def print_topology(devices: list) -> None:
if len(devices) < 2:
return
print_header("GPU topology and peer-to-peer")
for i, d0 in enumerate(devices):
for d1 in devices[i + 1 :]:
try:
level = system.get_topology_common_ancestor(d0, d1)
level_name = level.name
except Exception as e: # noqa: BLE001
level_name = f"unavailable ({e})"
try:
read = system.get_p2p_status(
d0, d1, GpuP2PCapsIndex.P2P_CAPS_INDEX_READ
)
write = system.get_p2p_status(
d0, d1, GpuP2PCapsIndex.P2P_CAPS_INDEX_WRITE
)
read_name = read.name
write_name = write.name
except Exception as e: # noqa: BLE001
read_name = write_name = f"unavailable ({e})"
print(
f"Device {d0.index} <-> Device {d1.index}: "
f"topology={level_name}, p2p_read={read_name}, p2p_write={write_name}"
)
def main() -> int:
import argparse
parser = argparse.ArgumentParser(
description="Print CUDA system / NVML information via cuda.core.system"
)
parser.add_argument(
"--no-topology",
action="store_true",
help="Skip cross-device topology/P2P queries",
)
args = parser.parse_args()
print_driver_info()
num_devices = system.get_num_devices()
print_header(f"Devices detected: {num_devices}")
if num_devices == 0:
print("No CUDA-capable devices found.")
return 0
if not CUDA_BINDINGS_NVML_IS_COMPATIBLE:
print(
"NVML is not compatible with the installed driver; skipping device detail."
)
return 0
devices = [system.Device(index=i) for i in range(num_devices)]
for device in devices:
print_device_info(device)
if not args.no_topology:
print_topology(devices)
print("\nDone")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,130 @@
# Sample: Vector Addition (Python)
## Description
Run your first GPU kernel: add two vectors element-wise on the GPU using the [`cuda.core`](https://nvidia.github.io/cuda-python/cuda-core/latest/) API with runtime compilation.
## What You'll Learn
- Writing CUDA kernels in C++ with template support
- Runtime compilation of CUDA kernels from Python
- Using `cuda.core` for device management, programs, and launches
- Configuring and launching kernels with grid and block dimensions
- Using CuPy for GPU memory management
- Verifying GPU results against CPU computation
## Key Libraries
- [`cuda.core`](https://nvidia.github.io/cuda-python/cuda-core/latest/) — Pythonic access to CUDA runtime and compilation
- `cupy` — GPU array library for Python
## Key APIs
### From `cuda.core`
- `Device` — Initialize and manage CUDA device
- `Program` — Create program from kernel source code
- `ProgramOptions` — Set compilation options (C++ standard, architecture)
- `LaunchConfig` — Configure kernel launch parameters
- `launch` — Execute kernel on specified stream
Import stable symbols from the top-level package (not `cuda.core.experimental`). See the [cuda.core documentation](https://nvidia.github.io/cuda-python/cuda-core/latest/).
### From CuPy
- `cp.random.rand()` — Generate random arrays on GPU
- `cp.empty()` — Allocate uninitialized GPU arrays
- `cp.allclose()` — Verify results with tolerance
### From `cuda_samples_utils`
- `verify_array_result()` — Verify computation results
## Kernel Techniques
- **1D Grid-Stride Loop** — Handle arbitrary array sizes with fixed grid
- **Template Programming** — Generic kernel for different data types
- **Bounds Checking** — Prevent out-of-bounds memory access
## Requirements
### Hardware
- NVIDIA GPU with Compute Capability 7.0 or higher
- Minimum GPU memory: 512 MB
### Software
- CUDA Toolkit 13.0 or newer (matches `cuda-python` 13.x)
- Python 3.10 or newer
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## Installation
Install the required packages from requirements.txt:
```bash
cd /path/to/cuda-samples/python/1_GettingStarted/vectorAdd
pip install -r requirements.txt
```
The requirements.txt installs:
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## How to Run
### Basic usage
```bash
cd samples/python/1_GettingStarted/vectorAdd
python vectorAdd.py
```
### With custom parameters
```bash
# Custom vector size
python vectorAdd.py --elements 1000000
# Use specific GPU
python vectorAdd.py --device 1
# Skip verification for benchmarking
python vectorAdd.py --no-verify
```
## Expected Output
```
[Vector addition using CUDA Core API]
Device: <Your GPU Name>
Compute Capability: sm_<XX>
Compiling kernel 'vectorAdd<float>'...
Kernel compiled successfully
[Vector addition of 50000 elements]
CUDA kernel launch with 196 blocks of 256 threads
Verifying result...
Test PASSED
Done
```
**Note:** Device name and compute capability will vary based on your GPU.
## Files
- `vectorAdd.py` — Python implementation using cuda.core API
- `README.md` — This file
- `requirements.txt` — Sample dependencies
- `../../Utilities/cuda_samples_utils.py` — Common utilities (imported by this sample)
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [cuda.core API](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- [CuPy Documentation](https://docs.cupy.dev/)

View File

@@ -0,0 +1,5 @@
# Vector Addition Sample Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0

View File

@@ -0,0 +1,196 @@
# 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.
"""
Vector Addition using CUDA Core API
This sample demonstrates element-wise vector addition: C = A + B
using cuda.core for runtime compilation and kernel launch.
"""
import sys
from pathlib import Path
# Add parent directory to path to import utilities
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
from cuda_samples_utils import verify_array_result # noqa: E402
try:
import cupy as cp
from cuda.core import Device, LaunchConfig, Program, ProgramOptions, launch
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
# CUDA kernel source code
VECTOR_ADD_KERNEL = """
/**
* CUDA Kernel for vector addition
* Computes the vector addition of A and B into C.
*/
template<typename T>
__global__ void vectorAdd(const T *A, const T *B, T *C, int numElements)
{
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < numElements)
{
C[i] = A[i] + B[i];
}
}
"""
def vector_add_cuda_core(num_elements=50000, device_id=0, verify=True):
"""
Perform vector addition using cuda.core API.
Parameters
----------
num_elements : int
Number of elements in each vector
device_id : int
CUDA device ID to use
verify : bool
Whether to verify the result
Returns
-------
bool
True if successful, False otherwise
"""
try:
# Initialize device
print("[Vector addition using CUDA Core API]")
device = Device(device_id)
device.set_current()
print(f"Device: {device.name}")
print(f"Compute Capability: sm_{device.arch}")
stream = device.create_stream()
# Compile kernel
print("Compiling kernel 'vectorAdd<float>'...")
program_options = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
program = Program(VECTOR_ADD_KERNEL, code_type="c++", options=program_options)
module = program.compile("cubin", name_expressions=("vectorAdd<float>",))
kernel = module.get_kernel("vectorAdd<float>")
print("Kernel compiled successfully")
# Allocate and initialize vectors
print(f"[Vector addition of {num_elements} elements]")
dtype = cp.float32
a = cp.random.rand(num_elements).astype(dtype)
b = cp.random.rand(num_elements).astype(dtype)
c = cp.empty(num_elements, dtype=dtype)
# Synchronize before kernel launch
device.sync()
# Configure and launch kernel
threads_per_block = 256
blocks_per_grid = (num_elements + threads_per_block - 1) // threads_per_block
print(
f"CUDA kernel launch with {blocks_per_grid} blocks "
f"of {threads_per_block} threads"
)
config = LaunchConfig(grid=blocks_per_grid, block=threads_per_block)
# Launch kernel
launch(
stream,
config,
kernel,
a.data.ptr,
b.data.ptr,
c.data.ptr,
cp.int32(num_elements),
)
stream.sync()
# Verify result
if verify:
print("Verifying result...")
expected = a + b
if not verify_array_result(c, expected):
return False
return True
except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""
Main entry point for the vector addition sample.
"""
import argparse
parser = argparse.ArgumentParser(description="Vector Addition using CUDA Core API")
parser.add_argument(
"--elements",
type=int,
default=50000,
help="Number of elements in vectors (default: 50000)",
)
parser.add_argument(
"--device", type=int, default=0, help="CUDA device ID (default: 0)"
)
parser.add_argument(
"--no-verify", action="store_true", help="Skip result verification"
)
args = parser.parse_args()
if args.elements <= 0:
print("Error: Number of elements must be positive")
return 1
success = vector_add_cuda_core(
num_elements=args.elements, device_id=args.device, verify=not args.no_verify
)
if success:
print("\nDone")
return 0
else:
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,102 @@
# Sample: Block-wise Array Sum (Python)
## Description
Demonstrates fundamental CUDA thread cooperation: thread/block indexing, strided loops, and block-wise reduction using shared memory. This sample shows three progressively complex kernel patterns using the **cuda.core API**:
1. **Simple indexing** - One thread per element
2. **Strided loop** - Each thread processes multiple elements
3. **Block partial sum** - Shared memory reduction within each block
## What You'll Learn
- How to calculate global thread ID from block and thread indices
- Strided loop pattern for processing arrays larger than grid size
- Block-level cooperation using shared memory and `__syncthreads()`
## Key Concepts
### Thread and Block Indexing
```
Global Thread ID = blockIdx.x * blockDim.x + threadIdx.x
Stride = blockDim.x * gridDim.x
```
### Strided Loop Pattern
Each thread processes multiple elements, enabling fixed grid size for arbitrary array lengths:
```c
for (size_t i = tid; i < N; i += stride) {
output[i] = input[i] * 2.0f;
}
```
## Key APIs
### From `cuda.core`:
- `Device` - Device management and context
- `Program` - Compile CUDA C++ kernels
- `ProgramOptions` - Kernel compilation options (architecture target)
- `LaunchConfig` - Configure grid/block dimensions and shared memory
- `launch()` - Execute kernel
- `EventOptions` - GPU timing configuration
### From CuPy:
- `cp.asarray()` - Transfer data to GPU
- `cp.zeros_like()` - Allocate GPU arrays
## Requirements
### Hardware:
- NVIDIA GPU with CUDA support
### Software:
- CUDA Toolkit 13.0 or newer
- Python 3.10 or newer
- See `requirements.txt` for Python packages
## Installation
```bash
pip install -r requirements.txt
```
## How to Run
```bash
python blockwiseSum.py
```
## Expected Output
```
Device: <Your GPU>
Compute Capability: sm_XX
Array size: 1,048,576 elements
Simple indexing: Test PASSED
Strided loop: Test PASSED
Block-wise sum: Test PASSED
Kernel time: X.XXX ms, Bandwidth: XXX.X GB/s
Done
```
## Files
- `blockwiseSum.py` - Python implementation with CUDA kernels
- `README.md` - This file
- `requirements.txt` - Sample dependencies
## See Also
- [cuda.core Documentation](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- [CUDA Shared Memory](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#shared-memory)
- [CuPy Documentation](https://docs.cupy.dev/)

View File

@@ -0,0 +1,259 @@
# 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.
"""
Block-wise Array Sum with Threaded Access
Demonstrates thread/block indexing, strided loops, and block-wise reduction.
Key Concepts:
Global Thread ID = blockIdx.x * blockDim.x + threadIdx.x
Stride = blockDim.x * gridDim.x
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
from cuda_samples_utils import verify_array_result
try:
import cupy as cp
import numpy as np
from cuda.core import (
Device,
EventOptions,
LaunchConfig,
Program,
ProgramOptions,
launch,
)
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Install with: pip install -r requirements.txt")
sys.exit(1)
KERNELS_CODE: str = r"""
// Each thread processes one element
extern "C" __global__
void simple_indexing(const float* input, float* output, size_t N) {
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid < N) {
output[tid] = input[tid] * 2.0f;
}
}
// Each thread processes multiple elements via strided access
extern "C" __global__
void strided_loop(const float* input, float* output, size_t N) {
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t stride = (size_t)blockDim.x * gridDim.x;
for (size_t i = tid; i < N; i += stride) {
output[i] = input[i] * 2.0f;
}
}
// Block-wise partial sum with shared memory reduction
extern "C" __global__
void block_partial_sum(const float* input, float* partial_sums, size_t N) {
extern __shared__ float sdata[];
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
unsigned int local_tid = threadIdx.x;
size_t stride = (size_t)blockDim.x * gridDim.x;
// Each thread accumulates multiple elements (strided)
float sum = 0.0f;
for (size_t i = tid; i < N; i += stride) {
sum += input[i];
}
sdata[local_tid] = sum;
__syncthreads();
// Block-level tree reduction
for (int s = blockDim.x / 2; s > 0; s >>= 1) {
if (local_tid < s) {
sdata[local_tid] += sdata[local_tid + s];
}
__syncthreads();
}
if (local_tid == 0) {
partial_sums[blockIdx.x] = sdata[0];
}
}
"""
def run_sample(num_elements: int = 1024 * 1024, device_id: int = 0) -> bool:
"""
Run block-wise sum demonstration.
Parameters
----------
num_elements : int
Number of array elements
device_id : int
CUDA device ID
Returns
-------
bool
True if all tests passed
"""
threads_per_block = 256
num_blocks = 64
device = Device(device_id)
device.set_current()
stream = device.create_stream()
arch = f"sm_{device.arch}"
print(f"Device: {device.name}")
print(f"Compute Capability: {arch}")
print(f"Array size: {num_elements:,} elements\n")
try:
# Make CuPy use our stream
cp.cuda.ExternalStream(int(stream.handle)).use()
# Compile kernels
program = Program(
KERNELS_CODE, code_type="c++", options=ProgramOptions(arch=arch)
)
module = program.compile(target_type="cubin")
kernel_simple = module.get_kernel("simple_indexing")
kernel_strided = module.get_kernel("strided_loop")
kernel_sum = module.get_kernel("block_partial_sum")
# Test data
h_input = np.arange(num_elements, dtype=np.float32)
d_input = cp.asarray(h_input)
d_output = cp.zeros_like(d_input)
expected = cp.asarray(h_input * 2.0)
# Demo 1: Simple indexing (1 thread = 1 element)
full_blocks = (num_elements + threads_per_block - 1) // threads_per_block
config = LaunchConfig(grid=full_blocks, block=threads_per_block)
launch(
stream,
config,
kernel_simple,
d_input.data.ptr,
d_output.data.ptr,
cp.uint64(num_elements),
)
stream.sync()
print("Simple indexing: ", end="")
test1 = verify_array_result(d_output, expected)
# Demo 2: Strided loop (threads process multiple elements)
d_output.fill(0)
config = LaunchConfig(grid=num_blocks, block=threads_per_block)
launch(
stream,
config,
kernel_strided,
d_input.data.ptr,
d_output.data.ptr,
cp.uint64(num_elements),
)
stream.sync()
print("Strided loop: ", end="")
test2 = verify_array_result(d_output, expected)
# Demo 3: Block-wise sum with shared memory
d_ones = cp.ones(num_elements, dtype=cp.float32)
d_partial = cp.zeros(num_blocks, dtype=cp.float32)
shared_mem = threads_per_block * 4
config = LaunchConfig(
grid=num_blocks, block=threads_per_block, shmem_size=shared_mem
)
launch(
stream,
config,
kernel_sum,
d_ones.data.ptr,
d_partial.data.ptr,
cp.uint64(num_elements),
)
stream.sync()
# Each block sums num_elements/num_blocks elements (strided access).
# Requires num_elements % num_blocks == 0 for correct expected values.
assert (
num_elements % num_blocks == 0
), "num_elements must be divisible by num_blocks for block_partial_sum"
expected_partial = cp.full(
num_blocks, num_elements / num_blocks, dtype=cp.float32
)
print("Block-wise sum: ", end="")
test3 = verify_array_result(d_partial, expected_partial)
# Performance timing
event_opts = EventOptions(enable_timing=True)
iterations = 100
stream.sync()
start = stream.record(options=event_opts)
for _ in range(iterations):
launch(
stream,
config,
kernel_sum,
d_ones.data.ptr,
d_partial.data.ptr,
cp.uint64(num_elements),
)
end = stream.record(options=event_opts)
end.sync()
time_ms = (end - start) / iterations
bandwidth = (num_elements * 4) / (time_ms * 1e6)
print(f"\nKernel time: {time_ms:.3f} ms, Bandwidth: {bandwidth:.1f} GB/s")
return test1 and test2 and test3
finally:
# Explicit resource cleanup
cp.cuda.Stream.null.use()
stream.close()
def main() -> None:
"""Entry point."""
success = run_sample()
if success:
print("\nDone")
else:
print("\nSome tests failed")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,6 @@
# Block-wise Array Sum Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0
numpy>=2.3.2

View File

@@ -0,0 +1,140 @@
# cudaGraphs (Python)
## Description
This sample demonstrates how to capture a multi-stage kernel pipeline as a
CUDA graph with `cuda.core` and replay it with a single driver call.
The sample runs a three-stage elementwise pipeline
`r3 = (a + b) * c - a` in two modes:
1. **Individual launches** - one `launch(stream, ...)` per stage, repeated
for every iteration of the pipeline.
2. **CUDA graph replay** - the same three launches are recorded into a
`Graph` once and replayed with `graph.launch(stream)` on each
iteration.
Both paths are timed over N iterations and their results are verified
against a reference computation. The sample also re-launches the graph
after mutating the input buffers to show that the graph captures
pointers (not data), so the same graph can process new inputs without
rebuilding.
## What You'll Learn
- Creating a `GraphBuilder` from a stream with `stream.create_graph_builder()`
- Capturing launches with `begin_building()` and `end_building()`
- Completing a graph with `builder.complete()` and uploading it to a stream
- Replaying the graph with `graph.launch(stream)`
- Measuring the launch-overhead savings for small kernels
- Re-running the same graph against updated input data
## Key Libraries
- [`cuda.core`](https://nvidia.github.io/cuda-python/cuda-core/latest/) - Pythonic access to CUDA runtime, programs, and graphs
- `cupy` - input buffers and result verification
- `numpy` - scalar kernel arguments
## Key APIs
### From `cuda.core`
- `Stream.create_graph_builder()` - obtain a `GraphBuilder`
- `GraphBuilder.begin_building()` / `end_building()` - begin and finish recording launches issued against the builder
- `GraphBuilder.complete()` - produce an executable `Graph`
- `Graph.upload(stream)` - upload the graph structure to the device
- `Graph.launch(stream)` - replay the entire graph
- `launch(graph_builder, config, kernel, ...)` - record a kernel launch into the graph being built
### From `cuda_samples_utils`
- `print_gpu_info()` - print device name and compute capability
## Requirements
### Hardware
- NVIDIA GPU with Compute Capability 7.0 or higher
- Minimum GPU memory: 512 MB
### Software
- CUDA Toolkit 13.0 or newer (matches `cuda-python` 13.x)
- Python 3.10 or newer
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## Installation
Install the required packages from `requirements.txt`:
```bash
cd /path/to/cuda-samples/python/2_CoreConcepts/cudaGraphs
pip install -r requirements.txt
```
The `requirements.txt` installs:
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## How to Run
### Basic usage
```bash
cd cuda-samples/python/2_CoreConcepts/cudaGraphs
python cudaGraphs.py
```
### With custom parameters
```bash
# Larger vectors and more iterations
python cudaGraphs.py --elements 4096 --iters 2000
# Use a specific GPU
python cudaGraphs.py --device 1
```
Short vectors exaggerate the launch-overhead savings; larger vectors
will show the two approaches converging because per-launch overhead
becomes negligible next to kernel runtime.
## Expected Output
Speedup numbers vary with GPU and host CPU.
```
Device: <Your GPU Name>
Compute Capability: <X.Y>
Individual launches: 1000 iters in 0.0085s (8.49 us/iter)
Building CUDA graph...
Graph replay: 1000 iters in 0.0034s (3.41 us/iter)
Graph speedup: 2.49x
Graph replay on updated data verified (same graph, new buffer contents)
Done
```
**Note:** Device name, compute capability, and speedup will vary based on
your GPU and host CPU.
## Files
- `cudaGraphs.py` - Python implementation using `cuda.core` CUDA graphs
- `README.md` - This file
- `requirements.txt` - Sample dependencies
- `../../Utilities/cuda_samples_utils.py` - Common utilities (imported by this sample)
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [`cuda.core` graphs API](https://nvidia.github.io/cuda-python/cuda-core/latest/api.html#cuda-graphs)
- Upstream `cuda.core` example: [`cuda_graphs.py`](https://github.com/NVIDIA/cuda-python/blob/main/cuda_core/examples/cuda_graphs.py)
- [CUDA Graphs programming guide](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cuda-graphs)

View File

@@ -0,0 +1,266 @@
# 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 Graphs with cuda.core
CUDA graphs let you record a DAG of operations once, then replay the entire
graph with a single driver call. For workflows that issue many small kernels
this can significantly reduce CPU-side launch overhead.
This sample runs a three-stage elementwise pipeline (add -> multiply ->
subtract) in two modes:
1. Individually launched kernels on a stream.
2. A single CUDA graph that captures the same three launches and is
replayed with ``graph.launch(stream)``.
We then measure the wall-clock time of each mode across many iterations to
illustrate the graph replay advantage for short kernels, and demonstrate that
a graph can be relaunched against new data (the pointers are baked in, but
the contents of those buffers are not).
"""
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
try:
import cupy as cp
import numpy as np
from cuda.core import Device, LaunchConfig, Program, ProgramOptions, launch
from cuda_samples_utils import print_gpu_info # noqa: E402
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
PIPELINE_KERNELS = r"""
extern "C" __global__
void vec_add(const float* A, const float* B, float* C, size_t N) {
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t stride = (size_t)gridDim.x * blockDim.x;
for (size_t i = tid; i < N; i += stride) C[i] = A[i] + B[i];
}
extern "C" __global__
void vec_mul(const float* A, const float* B, float* C, size_t N) {
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t stride = (size_t)gridDim.x * blockDim.x;
for (size_t i = tid; i < N; i += stride) C[i] = A[i] * B[i];
}
extern "C" __global__
void vec_sub(const float* A, const float* B, float* C, size_t N) {
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t stride = (size_t)gridDim.x * blockDim.x;
for (size_t i = tid; i < N; i += stride) C[i] = A[i] - B[i];
}
"""
def run_pipeline_individual(stream, kernels, config, buffers, size, n_iters):
"""Run the 3-stage pipeline `n_iters` times with one launch per stage."""
add_k, mul_k, sub_k = kernels
a, b, c, r1, r2, r3 = buffers
stream.sync()
t0 = time.perf_counter()
for _ in range(n_iters):
launch(
stream, config, add_k, a.data.ptr, b.data.ptr, r1.data.ptr, np.uint64(size)
)
launch(
stream, config, mul_k, r1.data.ptr, c.data.ptr, r2.data.ptr, np.uint64(size)
)
launch(
stream, config, sub_k, r2.data.ptr, a.data.ptr, r3.data.ptr, np.uint64(size)
)
stream.sync()
return time.perf_counter() - t0
def build_graph(stream, kernels, config, buffers, size):
"""Capture the 3-stage pipeline into a CUDA graph and return it."""
add_k, mul_k, sub_k = kernels
a, b, c, r1, r2, r3 = buffers
graph_builder = stream.create_graph_builder()
graph_builder.begin_building()
launch(
graph_builder,
config,
add_k,
a.data.ptr,
b.data.ptr,
r1.data.ptr,
np.uint64(size),
)
launch(
graph_builder,
config,
mul_k,
r1.data.ptr,
c.data.ptr,
r2.data.ptr,
np.uint64(size),
)
launch(
graph_builder,
config,
sub_k,
r2.data.ptr,
a.data.ptr,
r3.data.ptr,
np.uint64(size),
)
graph_builder.end_building()
graph = graph_builder.complete()
graph.upload(stream)
return graph_builder, graph
def run_pipeline_graph(stream, graph, n_iters):
"""Launch the compiled graph `n_iters` times."""
stream.sync()
t0 = time.perf_counter()
for _ in range(n_iters):
graph.launch(stream)
stream.sync()
return time.perf_counter() - t0
def main() -> int:
import argparse
parser = argparse.ArgumentParser(description="CUDA Graphs demo with cuda.core")
parser.add_argument(
"--elements",
type=int,
default=1 << 12,
help="Elements per vector (default: 4096 - small to emphasize launch overhead)",
)
parser.add_argument(
"--iters",
type=int,
default=1000,
help="Number of pipeline iterations to time (default: 1000)",
)
parser.add_argument("--device", type=int, default=0, help="CUDA device id")
args = parser.parse_args()
device = Device(args.device)
device.set_current()
print_gpu_info(device)
stream = device.create_stream()
# Tell CuPy to order its allocations on our stream so buffer initialization
# below is serialized with the kernels we launch.
cp.cuda.ExternalStream(int(stream.handle)).use()
graph_builder = graph = None
try:
program_options = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
program = Program(PIPELINE_KERNELS, code_type="c++", options=program_options)
module = program.compile("cubin")
add_k = module.get_kernel("vec_add")
mul_k = module.get_kernel("vec_mul")
sub_k = module.get_kernel("vec_sub")
kernels = (add_k, mul_k, sub_k)
N = args.elements
rng = cp.random.default_rng(seed=0)
a = rng.random(N, dtype=cp.float32)
b = rng.random(N, dtype=cp.float32)
c = rng.random(N, dtype=cp.float32)
r1 = cp.empty_like(a)
r2 = cp.empty_like(a)
r3 = cp.empty_like(a)
buffers = (a, b, c, r1, r2, r3)
expected = (a + b) * c - a
config = LaunchConfig(grid=(N + 255) // 256, block=256)
device.sync()
# Warm up compilation/caches, then measure individual launches.
run_pipeline_individual(stream, kernels, config, buffers, N, n_iters=5)
t_individual = run_pipeline_individual(
stream, kernels, config, buffers, N, n_iters=args.iters
)
assert cp.allclose(r3, expected, rtol=1e-5, atol=1e-5), (
"Individual pipeline produced incorrect results"
)
print(
f"\nIndividual launches: {args.iters} iters in {t_individual:.4f}s"
f" ({t_individual * 1e6 / args.iters:.2f} us/iter)"
)
# Capture the same pipeline as a graph and measure the replay.
print("\nBuilding CUDA graph...")
graph_builder, graph = build_graph(stream, kernels, config, buffers, N)
run_pipeline_graph(stream, graph, n_iters=5) # warm up
t_graph = run_pipeline_graph(stream, graph, n_iters=args.iters)
assert cp.allclose(r3, expected, rtol=1e-5, atol=1e-5), (
"Graph pipeline produced incorrect results"
)
print(
f"Graph replay: {args.iters} iters in {t_graph:.4f}s"
f" ({t_graph * 1e6 / args.iters:.2f} us/iter)"
)
if t_graph > 0:
print(f"Graph speedup: {t_individual / t_graph:.2f}x")
# Demonstrate that the graph replays against current buffer contents.
a[:] = cp.ones(N, dtype=cp.float32)
b[:] = cp.full(N, 2.0, dtype=cp.float32)
c[:] = cp.full(N, 3.0, dtype=cp.float32)
device.sync()
# r3 = (a + b) * c - a = (1 + 2) * 3 - 1 = 8
graph.launch(stream)
stream.sync()
assert cp.allclose(r3, 8.0), "Graph replay with new data produced wrong result"
print(
"\nGraph replay on updated data verified (same graph, new buffer contents)"
)
print("\nDone")
return 0
finally:
if graph is not None:
graph.close()
if graph_builder is not None:
graph_builder.close()
stream.close()
cp.cuda.Stream.null.use()
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,5 @@
# CUDA Graphs Sample Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0

View File

@@ -0,0 +1,136 @@
# Sample: FFT Signal Analysis (Python)
## Description
Analyze signal frequencies using Fast Fourier Transform (FFT) on the GPU. This sample demonstrates CuPy's cuFFT for GPU-accelerated frequency analysis: generating composite signals, computing magnitude spectrum, detecting dominant frequencies via peak detection, and comparing GPU vs CPU FFT performance.
## What You'll Learn
- Using CuPy's `cp.fft.rfft()` for real-to-complex FFT on GPU
- Computing magnitude spectrum from FFT results
- Peak detection to identify dominant frequencies
- Comparing GPU (cuFFT) vs CPU (NumPy) FFT performance
- Uses cuda.core APIs for device management and CUDA event timing
## Key Concepts
- **FFT (Fast Fourier Transform)**: Efficiently computes the Discrete Fourier Transform
- **Magnitude Spectrum**: `|FFT(signal)| * 2 / N` gives amplitude at each frequency
- **rfft**: Real FFT - optimized for real-valued input signals
- **Peak Detection**: Finding local maxima to identify dominant frequencies
### Stream Interop
This sample demonstrates CuPy integration with cuda.core streams:
```python
# Create stream with cuda.core
stream = device.create_stream()
# Use with CuPy operations
cp.cuda.ExternalStream(int(stream.handle)).use()
```
## Key APIs
### From `cuda.core`:
- `Device` - Device management and context
- `EventOptions` - Configure events for GPU timing
- `stream.record()` - Record events for timing
### From CuPy:
- `cp.fft.rfft()` - Real-to-complex FFT (GPU-accelerated via cuFFT)
- `cp.fft.rfftfreq()` - Generate frequency bins for rfft
- `cp.cuda.ExternalStream()` - Interop with cuda.core streams
### From NumPy:
- `np.fft.rfft()` - CPU FFT for comparison
## Requirements
### Hardware:
- NVIDIA GPU with CUDA support
### Software:
- CUDA Toolkit 13.0 or newer
- Python 3.10 or newer
- See `requirements.txt` for Python packages
## Installation
```bash
pip install -r requirements.txt
```
## How to Run
```bash
python fftSignalAnalysis.py
```
## Expected Output
```
============================================================
FFT Signal Analysis
============================================================
Device: <Your GPU>
Compute Capability: sm_XX
Signal Parameters:
Samples: 1,048,576
Sample Rate: 44,100 Hz
...
------------------------------------------------------------
GPU FFT (cuFFT)
------------------------------------------------------------
Time: X.XXX ms
Detected Frequencies:
440.0 Hz (magnitude: X.XXXX)
...
------------------------------------------------------------
CPU FFT (NumPy)
------------------------------------------------------------
Time: XX.XXX ms
------------------------------------------------------------
PERFORMANCE SUMMARY
------------------------------------------------------------
GPU (cuFFT): X.XXX ms
CPU (NumPy): XX.XXX ms
Speedup: XXx
------------------------------------------------------------
VERIFICATION
------------------------------------------------------------
GPU vs CPU FFT magnitude: Test PASSED
Frequency Detection Accuracy:
440 Hz: ✓
...
Done
```
**Note:** Times and speedup vary by hardware.
## Files
- `fftSignalAnalysis.py` - Main sample using cuda.core and CuPy
- `README.md` - This file
- `requirements.txt` - Dependencies
## See Also
- [cuda.core Documentation](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- [CuPy FFT Documentation](https://docs.cupy.dev/en/stable/reference/fft.html)
- [cuFFT Documentation](https://docs.nvidia.com/cuda/cufft/)

View File

@@ -0,0 +1,318 @@
# 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.
"""
FFT Signal Analysis
Demonstrates how to analyze signal frequencies using Fast Fourier Transform (FFT):
- Generate composite signals with multiple frequency components
- Use CuPy's cuFFT for GPU-accelerated frequency analysis
- Detect dominant frequencies (peak detection)
- Compare GPU vs CPU FFT performance
Uses cuda.core APIs for device management and timing.
"""
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
from cuda_samples_utils import verify_array_result
try:
import cupy as cp
import numpy as np
from cuda.core import Device, EventOptions
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Install with: pip install -r requirements.txt")
sys.exit(1)
def generate_composite_signal(
num_samples: int,
sample_rate: float,
frequencies: list[float],
amplitudes: list[float],
) -> np.ndarray:
"""
Generate a composite signal with multiple frequency components.
Parameters
----------
num_samples : int
Number of samples in the signal
sample_rate : float
Sampling rate in Hz
frequencies : list[float]
List of frequency components in Hz
amplitudes : list[float]
List of amplitudes for each frequency component
Returns
-------
np.ndarray
Signal array
"""
t = np.arange(num_samples, dtype=np.float32) / sample_rate
signal = np.zeros(num_samples, dtype=np.float32)
for freq, amp in zip(frequencies, amplitudes):
signal += amp * np.sin(2 * np.pi * freq * t)
return signal
def find_dominant_frequencies(
fft_magnitude: cp.ndarray,
frequencies: cp.ndarray,
num_peaks: int = 5,
threshold_ratio: float = 0.1,
) -> list[tuple[float, float]]:
"""
Find dominant frequencies from FFT magnitude spectrum.
Uses CPU-based peak detection (transfers magnitude/frequencies via cp.asnumpy).
Suitable for small-to-medium spectra; for large-scale analysis, consider
GPU-native peak detection.
Parameters
----------
fft_magnitude : cp.ndarray
Magnitude of FFT (positive frequencies only)
frequencies : cp.ndarray
Frequency bins
num_peaks : int
Maximum number of peaks to return
threshold_ratio : float
Minimum peak height as ratio of max peak
Returns
-------
list[tuple[float, float]]
List of (frequency, magnitude) tuples for detected peaks
"""
# Find peaks above threshold
max_magnitude = float(cp.max(fft_magnitude))
threshold = max_magnitude * threshold_ratio
# Simple peak detection: find local maxima above threshold
magnitude_cpu = cp.asnumpy(fft_magnitude)
freq_cpu = cp.asnumpy(frequencies)
peaks = []
for i in range(1, len(magnitude_cpu) - 1):
if magnitude_cpu[i] > threshold:
if (
magnitude_cpu[i] > magnitude_cpu[i - 1]
and magnitude_cpu[i] > magnitude_cpu[i + 1]
):
peaks.append((freq_cpu[i], magnitude_cpu[i]))
# Sort by magnitude and return top peaks
peaks.sort(key=lambda x: x[1], reverse=True)
return peaks[:num_peaks]
def run_fft_analysis(
num_samples: int = 2**20,
sample_rate: float = 44100.0,
device_id: int = 0,
num_iterations: int = 10,
) -> bool:
"""
Run FFT signal analysis benchmark.
device_id and num_iterations are not exposed via CLI; modify defaults
or call this function directly for customization.
Parameters
----------
num_samples : int
Number of samples (power of 2 recommended for FFT)
sample_rate : float
Sampling rate in Hz
device_id : int
CUDA device ID
num_iterations : int
Number of iterations for timing
Returns
-------
bool
True if analysis succeeded
"""
print("=" * 60)
print("FFT Signal Analysis")
print("=" * 60)
# Initialize device
device = Device(device_id)
device.set_current()
stream = device.create_stream()
try:
print(f"\nDevice: {device.name}")
print(f"Compute Capability: sm_{device.arch}")
# Make CuPy use our cuda.core stream
cp.cuda.ExternalStream(int(stream.handle)).use()
# Define test signal: composite of multiple frequencies
test_frequencies = [440.0, 880.0, 1320.0, 2000.0, 5000.0] # Hz
test_amplitudes = [1.0, 0.5, 0.3, 0.7, 0.4]
print("\nSignal Parameters:")
print(f" Samples: {num_samples:,}")
print(f" Sample Rate: {sample_rate:,.0f} Hz")
print(f" Duration: {num_samples / sample_rate:.3f} seconds")
print(f" Input Frequencies: {test_frequencies} Hz")
print(f" Input Amplitudes: {test_amplitudes}")
# Generate composite signal on CPU
h_signal = generate_composite_signal(
num_samples, sample_rate, test_frequencies, test_amplitudes
)
# Transfer to GPU
d_signal = cp.asarray(h_signal)
# ---------------------------------------------------------------------
# GPU FFT (cuFFT via CuPy)
# ---------------------------------------------------------------------
print("\n" + "-" * 60)
print("GPU FFT (cuFFT)")
print("-" * 60)
event_opts = EventOptions(enable_timing=True)
# Warmup
d_fft_result = cp.fft.rfft(d_signal)
stream.sync()
# Timed runs
start = stream.record(options=event_opts)
for _ in range(num_iterations):
d_fft_result = cp.fft.rfft(d_signal)
end = stream.record(options=event_opts)
end.sync()
gpu_time_ms = (end - start) / num_iterations
print(f"Time: {gpu_time_ms:.3f} ms")
# Compute magnitude spectrum
d_magnitude = cp.abs(d_fft_result) * 2 / num_samples
d_frequencies = cp.fft.rfftfreq(num_samples, 1 / sample_rate)
# Find dominant frequencies
detected_peaks = find_dominant_frequencies(d_magnitude, d_frequencies)
print("\nDetected Frequencies:")
for freq, mag in detected_peaks:
print(f" {freq:8.1f} Hz (magnitude: {mag:.4f})")
# ---------------------------------------------------------------------
# CPU FFT (NumPy) for comparison
# ---------------------------------------------------------------------
print("\n" + "-" * 60)
print("CPU FFT (NumPy)")
print("-" * 60)
# Warmup
h_fft_result = np.fft.rfft(h_signal)
# Timed runs
cpu_start = time.perf_counter()
for _ in range(num_iterations):
h_fft_result = np.fft.rfft(h_signal)
cpu_end = time.perf_counter()
cpu_time_ms = (cpu_end - cpu_start) * 1000 / num_iterations
print(f"Time: {cpu_time_ms:.3f} ms")
# ---------------------------------------------------------------------
# Performance Summary
# ---------------------------------------------------------------------
print("\n" + "-" * 60)
print("PERFORMANCE SUMMARY")
print("-" * 60)
speedup = cpu_time_ms / gpu_time_ms
print(f"GPU (cuFFT): {gpu_time_ms:.3f} ms")
print(f"CPU (NumPy): {cpu_time_ms:.3f} ms")
print(f"Speedup: {speedup:.1f}x")
# ---------------------------------------------------------------------
# Verification
# ---------------------------------------------------------------------
print("\n" + "-" * 60)
print("VERIFICATION")
print("-" * 60)
# Compare GPU and CPU results
h_magnitude = (
cp.asarray(np.abs(h_fft_result).astype(np.float32)) * 2 / num_samples
)
print("GPU vs CPU FFT magnitude: ", end="")
success = verify_array_result(
d_magnitude,
h_magnitude,
rtol=1e-4,
atol=1e-6,
)
# Verify detected frequencies match input
print("\nFrequency Detection Accuracy:")
detected_freqs = [freq for freq, _ in detected_peaks]
all_found = True
for expected_freq in test_frequencies:
found = any(abs(f - expected_freq) < 10 for f in detected_freqs)
status = "✓" if found else "✗"
print(f" {expected_freq:6.0f} Hz: {status}")
all_found = all_found and found
success = success and all_found
return success
finally:
# Cleanup - always close resources
cp.cuda.Stream.null.use()
stream.close()
def main() -> None:
"""Entry point."""
success = run_fft_analysis()
if success:
print("\nDone")
else:
print("\nAnalysis completed with errors")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,6 @@
# FFT Signal Analysis Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0
numpy>=2.3.2

View File

@@ -0,0 +1,250 @@
# greenContext (Python)
## Description
This sample demonstrates how to use **green contexts** with
`cuda.core` to statically partition a GPU's streaming multiprocessors
(SMs) so that independent kernels can run on dedicated subsets of the
device.
This examples takes A long-running kernel that fills the GPU's SMs,
and a short but latency-sensitive "critical" kernel is launched shortly after.
Without green contexts, the critical kernel must wait for SMs to
free up. With green contexts, the GPU's SMs are partitioned so the
critical kernel has its own dedicated SMs and can start immediately.
Three timed scenarios are compared:
1. **Reference**: the critical kernel alone on the primary context,
with no competing work. Establishes the pure compute time of the
critical kernel when every SM on the device is available to it.
2. **Baseline**: both kernels run on the device's primary context,
on two non-blocking streams that contend for all SMs.
3. **Green contexts**: the SMs are split into two disjoint groups
(e.g. 112 + 16). Each kernel runs on a stream that belongs to its
own green context, so the critical kernel never waits for SMs
held by the long-running kernel.
The headline metric is the total wall time of the critical kernel
from launch to completion. In the baseline it is dominated by time
spent waiting behind the long-running kernel. With green contexts it
reflects the kernel's own compute time on its (smaller) SM
partition. The reference row lets you separate those two effects:
- `baseline - reference` is roughly the time the critical kernel
spent waiting for SMs in the baseline run (the cost that green
contexts eliminate).
- `green / reference` is the compute slowdown caused by running on
a smaller SM partition (the cost that green contexts introduce).
## What You'll Learn
- Querying a device's SM resources via `Device.resources.sm` and
reading `sm_count`, `min_partition_size`, `coscheduled_alignment`
- Splitting an `SMResource` into disjoint partitions with
`sm.split(SMResourceOptions(count=(A, B)))`
- Creating a green context from an SM partition via
`Device.create_context(ContextOptions(resources=[group]))`
- Creating a non-blocking stream on a green context with
`ctx.create_stream()`
- Using CUDA events with timing enabled to measure kernel wall time
across streams
- Cleaning up green contexts safely with `ctx.close()`
## Key Libraries
- [`cuda.core`](https://nvidia.github.io/cuda-python/cuda-core/latest/) - device management, SM partitioning, green contexts, compilation, and launching
- `numpy` - scalar kernel arguments
## Key APIs
### From `cuda.core`
- `Device.resources.sm` - the device's SM-type device resource
- `SMResource.split(SMResourceOptions(count=(A, B)))` - partition SMs
into disjoint groups (plus an optional remainder)
- `Device.create_context(ContextOptions(resources=[sm_group]))` -
create a green context provisioned with a specific SM partition
- `Context.is_green` / `Context.resources` - introspect a green
context
- `Context.create_stream()` - create a non-blocking stream that is
tied to the green context's SM partition
- `Context.close()` - destroy a green context (must not be the
thread's current context when closed)
- `Device.create_event(EventOptions(timing_enabled=True))` /
`Stream.record(event)` / `event2 - event1` - measure elapsed time
in milliseconds between two events on the device
- `Program(..., ProgramOptions(std="c++17", arch=f"sm_{device.arch}"))`
/ `program.compile("cubin", name_expressions=(...))` - compile the
delay and critical kernels in one TU
- `launch(stream, LaunchConfig(grid=..., block=...), kernel, ...)` -
submit a kernel on a specific stream
## Requirements
### Hardware
- Any NVIDIA GPU supported by green contexts.
- Green-context SM partitioning is designed for larger server GPUs
(H100, H200, B200, ...) but works on any supported GPU as long as
the SM count is large enough to split meaningfully.
### Software
- NVIDIA driver >= 12.4
- CUDA Toolkit 13.0 or newer.
- Python 3.10 or newer.
- `cuda-core` (`>=1.0.0`)
## Installation
Install the required packages from `requirements.txt`:
```bash
cd /path/to/cuda-samples/python/2_CoreConcepts/greenContext
pip install -r requirements.txt
```
## How to Run
### Basic usage
The auto-default split reserves a small partition (~16 SMs) for the
critical kernel and gives the rest to the long-running kernel. The
exact sizes are chosen by probing the driver with a dry-run `sm.split`,
escalating the alignment granularity in powers of two until the driver
accepts the pair. This handles architectures where the driver enforces
stricter alignment (e.g. TPC/GPC-pair alignment on Blackwell) than the
reported `min_partition_size`. When that happens the sample prints a
`Note:` line with the granularity it landed on.
```bash
cd cuda-samples/python/2_CoreConcepts/greenContext
python greenContext.py
```
### Match the CUDA programming guide example (112 + 16)
```bash
python greenContext.py --split 112,16
```
### Tune the workload
```bash
# Longer long-running kernel, larger host launch gap
python greenContext.py --delay-us 3000 --launch-gap-ms 2.0
# Smaller/lighter critical kernel so its own compute time is negligible
python greenContext.py --critical-n 65536 --critical-iters 128
# Symmetric split: maximum SMs for the critical kernel, long kernel is
# roughly 2x slower but the critical kernel runs close to its reference time.
python greenContext.py --split 64,64
# Use a specific GPU
python greenContext.py --device 1
```
### All options
```
--device CUDA device ID (default: 0)
--split SM split as 'LONG,CRITICAL', e.g. '112,16'.
Each side must be a multiple of the device's
min_partition_size, and the driver may enforce additional
architecture-specific alignment (e.g. TPC/partition-grid
alignment on Blackwell). Omit --split to auto-select a
driver-accepted split.
--delay-us Per-block busy-wait of the delay kernel, in us (default: 2000)
--delay-waves Number of waves of the delay kernel on the long
partition. Drives the default --delay-blocks (default: 16)
--delay-blocks Number of blocks for the delay kernel. Overrides
--delay-waves if set.
(default: --delay-waves * device SM count)
--critical-n Work size of the critical kernel (default: 4194304)
--critical-iters Inner math-loop iterations inside the critical kernel.
Higher values make the critical kernel's own compute
time more substantial relative to its wait time
(default: 1024)
--launch-gap-ms Host delay between launching the long and critical
kernels (default: 1.0 ms)
```
## Expected Output
The output depends on the GPU and the number of SMs.
On an RTX 4090 (128 SMs) with the default auto split:
```
[Green Context Sample using CUDA Core API]
Device: NVIDIA GeForce RTX 4090
Compute Capability: sm_89
Total SMs: 128
Min. SM partition size: 2
SM co-scheduled alignment: 2
SM split (long/critical): 112 / 16
Workload parameters:
delay kernel: 2048 blocks, 2000 us/block (~32.0 ms on 128 SMs)
critical kernel: 4194304 elements, 1024 inner iterations
host launch gap: 1.0 ms
Compiling kernels ...
Running reference scenario (critical kernel alone) ...
Running baseline scenario (primary context) ...
Running green context scenario ...
scenario SMs (long/crit) long (ms) crit total (ms) crit offset (ms)
-------------------------------------------------------------------------------------------------------
crit alone (primary ctx) -/128 - 0.425 -
baseline (primary ctx) 128/128 32.034 30.024 1.090
green ctx (112+16 SMs) 112/16 38.017 2.696 1.075
long (ms) : wall time of the delay kernel
crit total (ms) : launch-to-complete wall time of the critical kernel
crit offset (ms) : when the critical stream started, relative to the long stream start
Critical-kernel latency speedup (baseline vs green ctx): 11.14x
Green-ctx compute cost vs unconstrained (crit alone): 6.34x
Baseline time spent waiting for SMs (not computing): ~29.60 ms
Done
```
**What to look for:**
- The critical kernel alone (reference row) takes only a fraction of
a millisecond; almost all of the baseline's `crit total` is time
spent queued waiting for SMs, not compute.
- The **critical kernel's wall time drops sharply** in the
green-context scenario (from ~30 ms to a few ms in the example
above) because it no longer waits for SMs held by the long-running
kernel.
- The **long-running kernel's duration may increase** proportional
to the reduction in SMs available to it (128 -> 112 SMs ~= 14%
slower; 128 -> 64 SMs ~= 2x slower). This is an expected tradeoff:
you reserve SMs for a critical kernel by taking them away from the
background workload.
- The **compute cost** ratio (`green / reference`) shows how close
the critical kernel is to ideal linear scaling with its SM count.
A 112/16 split gives the critical kernel only 12.5% of the SMs and
costs it roughly 6-7x its reference time; a 64/64 split gives it
half the SMs and costs roughly 1.5-2x.
- The `crit offset` column is approximately `--launch-gap-ms` in
both full scenarios; it confirms the host launched the critical
kernel the same amount of time after the long kernel in both runs.
Exact timings vary with GPU model, driver version, clock state, and
other concurrent GPU work.
## Files
- `greenContext.py` - Python implementation using `cuda.core` green-context APIs
- `README.md` - This file
- `requirements.txt` - Sample dependencies
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [Green Contexts in the CUDA C++ Programming Guide](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#green-contexts)
- [`cuda.core` green-context test suite](https://github.com/NVIDIA/cuda-python/blob/main/cuda_core/tests/test_green_context.py) - the authoritative API reference

View File

@@ -0,0 +1,752 @@
# 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.
"""
Green Context Sample using CUDA Core API.
Three scenarios are timed with CUDA events and compared:
1. Reference: the critical kernel alone on the primary context,
with no competing work. Establishes the pure compute time of
the critical kernel with access to every SM on the device.
2. Baseline: both kernels run on the device's primary context,
on two non-blocking streams. They contend for all SMs.
3. Green contexts: SMs are split into two disjoint groups; each
kernel runs on a stream belonging to its own green context.
The headline metric is the total wall time of the critical kernel
from launch to completion on its stream. In the baseline it is
dominated by waiting behind the long-running kernel; with green
contexts it reflects only the kernel's own compute time on a
smaller SM partition. The reference row separates those effects.
Note: Parallel execution on the GPU is never guaranteed. Green
contexts remove one common source of contention (shared SMs) but
they are not a hard scheduling promise.
"""
import argparse
import sys
import time
from dataclasses import dataclass
from typing import List, Optional, Tuple
import numpy as np
from cuda.core import (
ContextOptions,
Device,
EventOptions,
LaunchConfig,
Program,
ProgramOptions,
SMResourceOptions,
launch,
)
# Two CUDA kernels:
# 1. The delay kernel spins until `cycles` SM clock ticks have elapsed.
# 2. The critical kernel does a small amount of useful work.
KERNEL_SRC = r"""
extern "C" __global__ void delay_kernel(unsigned long long cycles)
{
unsigned long long start = clock64();
while ((unsigned long long)(clock64() - start) < cycles) { }
}
extern "C" __global__ void critical_kernel(float *out, int n, int iters)
{
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
// Two dependent accumulators so the compiler cannot collapse the
// loop into a closed-form expression. `iters` is a runtime argument
// for the same reason.
float v = (float)i * 1e-6f + 1.0f;
float u = (float)i * 1e-7f + 0.5f;
for (int k = 0; k < iters; ++k) {
v = v * 1.000001f + u;
u = u * 0.999999f + v * 1e-7f;
}
out[i] = v + u;
}
}
"""
@dataclass
class ScenarioResult:
name: str
critical_total_ms: float
critical_sm_count: int
long_ms: Optional[float] = None
critical_offset_ms: Optional[float] = None
long_sm_count: Optional[int] = None
def print_sm_topology(device: Device) -> None:
sm = device.resources.sm
print("[Green Context Sample using CUDA Core API]")
print(f"Device: {device.name}")
print(f"Compute Capability: sm_{device.arch}")
print(f"Total SMs: {sm.sm_count}")
print(f"Min. SM partition size: {sm.min_partition_size}")
print(f"SM co-scheduled alignment: {sm.coscheduled_alignment}")
def _align_down(n: int, k: int) -> int:
if k <= 0:
return n
return (n // k) * k
def _driver_accepts_split(sm, long_count: int, critical_count: int) -> bool:
if long_count <= 0 or critical_count <= 0:
return False
try:
groups, _ = sm.split(
SMResourceOptions(count=(long_count, critical_count)),
dry_run=True,
)
except Exception:
return False
actual = tuple(g.sm_count for g in groups)
return actual == (long_count, critical_count)
def _find_working_split(
sm, prefer_critical: Optional[int] = None
) -> Optional[Tuple[int, int, int]]:
"""
Probe the driver for a (long, critical) split it actually accepts.
Escalates the alignment granularity from `min_partition_size` upward in
powers of two, requiring BOTH sides to be multiples of the current
granularity. This handles architectures where the driver's true
allocation granularity is larger than the reported
`min_partition_size` (e.g. TPC/GPC-pair alignment on Blackwell: on a
188-SM part `min_partition_size` is 8 but the driver actually requires
each side to be a multiple of 16).
Returns (long_count, critical_count, granularity) or None. The
granularity is the smallest power-of-two multiple of
`min_partition_size` at which both sides are aligned and the driver
accepts the pair.
"""
total = sm.sm_count
min_part = sm.min_partition_size
if min_part <= 0:
return None
if prefer_critical is None or prefer_critical <= 0:
prefer_critical = max(min_part, min(16, total // 8))
# Escalate granularity in powers of two. The upper bound is half of
# `total` because below that we cannot fit two partitions of size
# >= granularity.
granularity = min_part
while granularity * 2 <= total:
base = max(granularity, _align_down(prefer_critical, granularity))
candidates: List[int] = []
seen = set()
def push(c: int) -> None:
if c >= granularity and c <= total - granularity and c not in seen:
seen.add(c)
candidates.append(c)
# Walk outward from `base` (the preferred critical size, aligned
# down to the current granularity) in steps of granularity.
push(base)
max_steps = max(total // granularity, 1)
for step in range(1, max_steps + 1):
push(base + step * granularity)
push(base - step * granularity)
for critical in candidates:
long_count = _align_down(total - critical, granularity)
if long_count < granularity:
continue
if _driver_accepts_split(sm, long_count, critical):
return long_count, critical, granularity
granularity *= 2
return None
def _format_suggestion(sm, prefer_critical: Optional[int]) -> Optional[str]:
"""
Return a '--split A,B' string the driver is known to accept, or None
if we couldn't find one.
"""
found = _find_working_split(sm, prefer_critical=prefer_critical)
if found is None:
return None
long_count, critical_count, _granularity = found
return f"--split {long_count},{critical_count}"
def parse_split(arg: Optional[str], device: Device) -> Tuple[int, int]:
"""
Parse the --split "A,B" CLI argument and validate it against the device.
Returns (long_count, critical_count).
"""
sm = device.resources.sm
total = sm.sm_count
min_part = sm.min_partition_size
if arg is None:
# Auto: reserve a small aligned slice for the critical kernel and
# hand the rest (also aligned) to the long-running kernel. We
# can't trust `min_partition_size` alone: on some GPUs (e.g.
# 188-SM Blackwell) the driver requires stricter alignment than
# it reports. Escalate the granularity until the driver accepts
# a pair.
prefer_critical = max(min_part, min(16, total // 8))
found = _find_working_split(sm, prefer_critical=prefer_critical)
if found is None:
print(
"Error: could not find an SM split that the driver accepts "
f"on this device (total SMs={total}, "
f"min_partition_size={min_part})."
)
print(
" The driver enforces architecture-specific alignment "
"rules beyond min_partition_size; try passing an explicit "
"--split."
)
sys.exit(1)
long_count, critical_count, granularity = found
if granularity > min_part:
print(
f"Note: driver required stricter alignment than "
f"min_partition_size={min_part}; selected split uses "
f"granularity={granularity} SMs."
)
return long_count, critical_count
# User-provided split.
try:
parts = [int(x.strip()) for x in arg.split(",")]
except ValueError:
print(f"Error: --split must look like 'A,B', got: {arg!r}")
sys.exit(1)
if len(parts) != 2:
print(
"Error: --split must contain exactly two comma-separated "
f"integers, got: {arg!r}"
)
sys.exit(1)
long_count, critical_count = parts
errors = []
if long_count <= 0 or critical_count <= 0:
errors.append("both partition sizes must be positive")
if long_count % min_part != 0 or critical_count % min_part != 0:
errors.append(f"each size must be a multiple of min_partition_size={min_part}")
if long_count + critical_count > total:
errors.append(
f"sum {long_count + critical_count} exceeds device total of {total} SMs"
)
if errors:
print("Error: invalid --split value:")
for e in errors:
print(f" - {e}")
suggestion = _format_suggestion(
sm, prefer_critical=critical_count if critical_count > 0 else None
)
if suggestion is not None:
print(f"Tip: a driver-accepted split on this device is {suggestion}")
sys.exit(1)
# Confirm the driver itself accepts the split. The well-known alignment
# checks above are necessary but not sufficient on every architecture.
try:
groups, _ = sm.split(
SMResourceOptions(count=(long_count, critical_count)),
dry_run=True,
)
except Exception as e:
print(f"Error: driver rejected the requested split: {e}")
print(
" The sample's own alignment checks are not exhaustive on "
"every architecture; the driver enforces additional hardware "
"constraints (for example TPC/partition-grid alignment)."
)
suggestion = _format_suggestion(sm, prefer_critical=critical_count)
if suggestion is not None:
print(f"Tip: a driver-accepted split on this device is {suggestion}")
sys.exit(1)
actual = tuple(g.sm_count for g in groups)
if actual != (long_count, critical_count):
print(f"Error: driver adjusted the requested split to {actual}.")
suggestion = _format_suggestion(sm, prefer_critical=critical_count)
if suggestion is not None:
print(f"Tip: a driver-accepted split on this device is {suggestion}")
else:
print(" Pick a different --split, or omit it for the auto default.")
sys.exit(1)
return long_count, critical_count
def compile_kernels(device: Device):
options = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
program = Program(KERNEL_SRC, code_type="c++", options=options)
module = program.compile(
"cubin",
name_expressions=("delay_kernel", "critical_kernel"),
)
return module.get_kernel("delay_kernel"), module.get_kernel("critical_kernel")
def microseconds_to_cycles(device: Device, microseconds: float) -> int:
"""
Convert microseconds to SM clock cycles, using the reported GPU clock rate.
clock_rate is in kHz, so 1 us = clock_rate_kHz / 1000 cycles.
"""
clock_khz = device.properties.clock_rate
return int(microseconds * clock_khz / 1000.0)
def _run_one(
device: Device,
name: str,
long_stream,
critical_stream,
long_sm_count: int,
critical_sm_count: int,
delay_kernel,
critical_kernel,
delay_cycles: int,
delay_blocks: int,
critical_out_ptr: int,
critical_n: int,
critical_iters: int,
launch_gap_s: float,
) -> ScenarioResult:
"""
Launch the delay kernel on `long_stream`, wait `launch_gap_s` on the host,
launch the critical kernel on `critical_stream`, and time both with events.
"""
# Create events with timing enabled.
opts = EventOptions(timing_enabled=True)
e_long_start = device.create_event(opts)
e_long_end = device.create_event(opts)
e_crit_start = device.create_event(opts)
e_crit_end = device.create_event(opts)
# 1024 threads/block ensures at most one delay block is resident per SM
# on current architectures, so grid size directly controls the number of
# waves: delay_blocks / sm_count_visible_to_stream.
delay_block = 1024
delay_cfg = LaunchConfig(grid=delay_blocks, block=delay_block)
critical_block = 256
critical_grid = (critical_n + critical_block - 1) // critical_block
critical_cfg = LaunchConfig(grid=critical_grid, block=critical_block)
# Start of timed region
long_stream.record(e_long_start)
launch(long_stream, delay_cfg, delay_kernel, np.uint64(delay_cycles))
long_stream.record(e_long_end)
time.sleep(launch_gap_s)
critical_stream.record(e_crit_start)
launch(
critical_stream,
critical_cfg,
critical_kernel,
critical_out_ptr,
np.int32(critical_n),
np.int32(critical_iters),
)
critical_stream.record(e_crit_end)
# Sync both streams so every event has completed and is measurable.
long_stream.sync()
critical_stream.sync()
# End of timed region
return ScenarioResult(
name=name,
long_ms=e_long_end - e_long_start,
critical_total_ms=e_crit_end - e_crit_start,
critical_offset_ms=e_crit_start - e_long_start,
long_sm_count=long_sm_count,
critical_sm_count=critical_sm_count,
)
def run_critical_alone(
device: Device,
critical_kernel,
critical_n: int,
critical_iters: int,
) -> ScenarioResult:
"""
Critical kernel alone on the primary context, no competing work.
Establishes the pure compute time with every SM on the device available.
"""
stream = device.create_stream()
out = device.allocate(critical_n * 4)
total_sm = device.resources.sm.sm_count
try:
opts = EventOptions(timing_enabled=True)
e_start = device.create_event(opts)
e_end = device.create_event(opts)
block = 256
grid = (critical_n + block - 1) // block
cfg = LaunchConfig(grid=grid, block=block)
stream.record(e_start)
launch(
stream,
cfg,
critical_kernel,
int(out.handle),
np.int32(critical_n),
np.int32(critical_iters),
)
stream.record(e_end)
stream.sync()
return ScenarioResult(
name="crit alone (primary ctx)",
critical_total_ms=e_end - e_start,
critical_sm_count=total_sm,
)
finally:
out.close()
def run_baseline(
device: Device,
delay_kernel,
critical_kernel,
delay_cycles: int,
delay_blocks: int,
critical_n: int,
critical_iters: int,
launch_gap_s: float,
) -> ScenarioResult:
"""Both kernels on the primary context, two non-blocking streams."""
long_stream = device.create_stream()
critical_stream = device.create_stream()
out = device.allocate(critical_n * 4)
total_sm = device.resources.sm.sm_count
try:
return _run_one(
device,
name="baseline (primary ctx)",
long_stream=long_stream,
critical_stream=critical_stream,
long_sm_count=total_sm,
critical_sm_count=total_sm,
delay_kernel=delay_kernel,
critical_kernel=critical_kernel,
delay_cycles=delay_cycles,
delay_blocks=delay_blocks,
critical_out_ptr=int(out.handle),
critical_n=critical_n,
critical_iters=critical_iters,
launch_gap_s=launch_gap_s,
)
finally:
out.close()
def run_green_context(
device: Device,
split: Tuple[int, int],
delay_kernel,
critical_kernel,
delay_cycles: int,
delay_blocks: int,
critical_n: int,
critical_iters: int,
launch_gap_s: float,
) -> ScenarioResult:
"""Each kernel on its own green context, with disjoint SM partitions."""
long_count, critical_count = split
sm = device.resources.sm
groups, _remainder = sm.split(SMResourceOptions(count=(long_count, critical_count)))
assert len(groups) == 2
long_group, critical_group = groups
# Create the large ctx last so it's closed first: order matters only for
# ensuring we never try to close a ctx that's currently the thread's
# active ctx.
ctx_long = device.create_context(ContextOptions(resources=[long_group]))
ctx_crit = None
out = None
try:
ctx_crit = device.create_context(ContextOptions(resources=[critical_group]))
long_stream = ctx_long.create_stream()
critical_stream = ctx_crit.create_stream()
out = device.allocate(critical_n * 4)
return _run_one(
device,
name=f"green ctx ({long_count}+{critical_count} SMs)",
long_stream=long_stream,
critical_stream=critical_stream,
long_sm_count=ctx_long.resources.sm.sm_count,
critical_sm_count=ctx_crit.resources.sm.sm_count,
delay_kernel=delay_kernel,
critical_kernel=critical_kernel,
delay_cycles=delay_cycles,
delay_blocks=delay_blocks,
critical_out_ptr=int(out.handle),
critical_n=critical_n,
critical_iters=critical_iters,
launch_gap_s=launch_gap_s,
)
finally:
if out is not None:
out.close()
# Streams must be released before their owning ctx; letting them go out
# of scope here is sufficient since no references escape this frame.
if ctx_crit is not None:
ctx_crit.close()
ctx_long.close()
def _fmt_ms(value: Optional[float], width: int) -> str:
if value is None:
return f"{'-':>{width}}"
return f"{value:>{width}.3f}"
def print_results(results: List[ScenarioResult]) -> None:
print()
header = (
f"{'scenario':<32}{'SMs (long/crit)':>20}"
f"{'long (ms)':>14}{'crit total (ms)':>18}{'crit offset (ms)':>19}"
)
print(header)
print("-" * len(header))
for r in results:
long_sm = "-" if r.long_sm_count is None else str(r.long_sm_count)
sms = f"{long_sm}/{r.critical_sm_count}"
print(
f"{r.name:<32}{sms:>20}"
f"{_fmt_ms(r.long_ms, 14)}{_fmt_ms(r.critical_total_ms, 18)}"
f"{_fmt_ms(r.critical_offset_ms, 19)}"
)
print()
print("long (ms) : wall time of the delay kernel")
print("crit total (ms) : launch-to-complete wall time of the critical kernel")
print(
"crit offset (ms) : when the critical stream started, relative to the"
" long stream start"
)
def report_speedup(
alone: ScenarioResult,
baseline: ScenarioResult,
green: ScenarioResult,
) -> None:
"""
Print three headline numbers that put the raw scenario timings in context:
"""
if baseline.critical_total_ms <= 0 or alone.critical_total_ms <= 0:
return
latency_speedup = baseline.critical_total_ms / max(green.critical_total_ms, 1e-9)
compute_cost = green.critical_total_ms / alone.critical_total_ms
wait_ms = max(0.0, baseline.critical_total_ms - alone.critical_total_ms)
print()
print(
f"Critical-kernel latency speedup (baseline vs green ctx): "
f"{latency_speedup:.2f}x"
)
print(
f"Green-ctx compute cost vs unconstrained (crit alone): {compute_cost:.2f}x"
)
print(f"Baseline time spent waiting for SMs (not computing): ~{wait_ms:.2f} ms")
def main():
parser = argparse.ArgumentParser(
description="Green Context sample using CUDA Core API",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--device", type=int, default=0, help="CUDA device ID (default: 0)"
)
parser.add_argument(
"--split",
type=str,
default=None,
help="SM split as 'LONG,CRITICAL', e.g. '112,16'. Default: auto.",
)
parser.add_argument(
"--delay-us",
type=int,
default=2000,
help=(
"Per-block busy-wait duration of the delay kernel, "
"in microseconds (default: 2000)"
),
)
parser.add_argument(
"--delay-waves",
type=int,
default=16,
help=(
"Number of waves of the delay kernel on the long partition. "
"Drives the default --delay-blocks (default: 16)."
),
)
parser.add_argument(
"--delay-blocks",
type=int,
default=None,
help=(
"Number of blocks launched for the delay kernel. "
"Overrides --delay-waves if set. "
"Default: --delay-waves * device SM count."
),
)
parser.add_argument(
"--critical-n",
type=int,
default=1 << 22,
help="Work size of the critical kernel (default: 4194304)",
)
parser.add_argument(
"--critical-iters",
type=int,
default=1024,
help=(
"Iterations of the inner math loop inside the critical kernel. "
"Higher values make the critical kernel's compute time more "
"substantial (default: 1024)."
),
)
parser.add_argument(
"--launch-gap-ms",
type=float,
default=1.0,
help=(
"Host delay between launching the long and critical kernels, "
"in ms (default: 1.0)"
),
)
args = parser.parse_args()
try:
device = Device(args.device)
device.set_current()
except Exception as e:
print(f"Error: failed to initialize CUDA device {args.device}: {e}")
return 1
print_sm_topology(device)
long_count, critical_count = parse_split(args.split, device)
print(f"SM split (long/critical): {long_count} / {critical_count}")
sm_count = device.resources.sm.sm_count
delay_blocks = args.delay_blocks or args.delay_waves * sm_count
delay_cycles = microseconds_to_cycles(device, args.delay_us)
launch_gap_s = max(0.0, args.launch_gap_ms / 1000.0)
# Rough estimate of the long kernel's duration on the full device. Mostly
# informational; the real value is reported after the run.
est_long_ms = (delay_blocks / sm_count) * (args.delay_us / 1000.0)
print("Workload parameters:")
print(
f" delay kernel: {delay_blocks} blocks, {args.delay_us} us/block "
f"(~{est_long_ms:.1f} ms on {sm_count} SMs)"
)
print(
f" critical kernel: {args.critical_n} elements, "
f"{args.critical_iters} inner iterations"
)
print(f" host launch gap: {args.launch_gap_ms} ms")
print()
print("Compiling kernels ...")
delay_k, crit_k = compile_kernels(device)
try:
print("Running reference scenario (critical kernel alone) ...")
alone = run_critical_alone(
device,
crit_k,
args.critical_n,
args.critical_iters,
)
print("Running baseline scenario (primary context) ...")
baseline = run_baseline(
device,
delay_k,
crit_k,
delay_cycles,
delay_blocks,
args.critical_n,
args.critical_iters,
launch_gap_s,
)
print("Running green context scenario ...")
green = run_green_context(
device,
(long_count, critical_count),
delay_k,
crit_k,
delay_cycles,
delay_blocks,
args.critical_n,
args.critical_iters,
launch_gap_s,
)
except Exception as e:
print(f"Error: scenario failed: {e}")
import traceback
traceback.print_exc()
return 1
print_results([alone, baseline, green])
report_speedup(alone, baseline, green)
print("\nDone")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,3 @@
cuda-python>=13.0.0
cuda-core>=0.7.0
numpy>=2.3.2

View File

@@ -0,0 +1,133 @@
# JIT Compilation and Link-Time Optimization (Python)
## Description
This sample demonstrates how to build a kernel out of two independently
compiled translation units and link them at runtime with
`cuda.core.Linker`. This is the pattern a library would use to accept
user-supplied device code as a plug-in without recompiling its own
kernels from scratch.
The sample runs the same program in two linking modes:
1. **PTX linking** - each module is compiled with
`ProgramOptions(relocatable_device_code=True)` down to PTX, and the
`Linker` emits a final cubin. The two modules stay independently
compiled (no cross-module inlining).
2. **Link-Time Optimization (LTO)** - each module is compiled with
`ProgramOptions(link_time_optimization=True)` down to LTO IR, and the
`Linker` is configured with `LinkerOptions(link_time_optimization=True)`
so the optimizer runs again across both modules, typically matching
the code generation of a single-source build.
The "main" kernel `apply_transform` calls a `user_transform` device
function that lives in a separate source string, and the results of both
linking modes are verified against a NumPy reference.
## What You'll Learn
- Compiling multiple `Program` objects into PTX or LTO IR
- Linking independent object codes into a single cubin with `Linker`
- Choosing between `relocatable_device_code` and `link_time_optimization`
- How a library's main kernel can call into user-supplied device code
- When to prefer LTO over plain PTX linking
## Key Libraries
- [`cuda.core`](https://nvidia.github.io/cuda-python/cuda-core/latest/) - Pythonic access to CUDA runtime, programs, and the JIT linker
- `cupy` - input and output buffers on the GPU
- `numpy` - reference computation on the host
## Key APIs
### From `cuda.core`
- `ProgramOptions(relocatable_device_code=True)` + `Program.compile("ptx")` - produce relocatable PTX
- `ProgramOptions(link_time_optimization=True)` + `Program.compile("ltoir")` - produce LTO IR
- `Linker(*object_codes, options=LinkerOptions(...))` - create a JIT linker over multiple object codes
- `LinkerOptions(link_time_optimization=True)` - opt into LTO during linking
- `Linker.link("cubin")` - produce a loadable module
- `ObjectCode.get_kernel(name)` - fetch a kernel from the linked module
### From `cuda_samples_utils`
- `print_gpu_info()` - print device name and compute capability
## Requirements
### Hardware
- NVIDIA GPU with Compute Capability 7.0 or higher
### Software
- CUDA Toolkit 13.0 or newer (matches `cuda-python` 13.x)
- Python 3.10 or newer
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## Installation
Install the required packages from `requirements.txt`:
```bash
cd /path/to/cuda-samples/python/2_CoreConcepts/jitLtoLinking
pip install -r requirements.txt
```
The `requirements.txt` installs:
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## How to Run
### Basic usage
```bash
cd cuda-samples/python/2_CoreConcepts/jitLtoLinking
python jitLtoLinking.py
```
### With custom parameters
```bash
# Larger element count
python jitLtoLinking.py --elements 1048576
# Use a specific GPU
python jitLtoLinking.py --device 1
```
## Expected Output
```
Device: <Your GPU Name>
Compute Capability: <X.Y>
[1] PTX linking (no LTO)
[ptx] result verified against NumPy reference
[2] LTO linking (link-time optimization)
[lto] result verified against NumPy reference
Both PTX and LTO linked kernels produced matching results. Done
```
**Note:** Device name and compute capability will vary based on your GPU.
## Files
- `jitLtoLinking.py` - Python implementation using `cuda.core.Linker`
- `README.md` - This file
- `requirements.txt` - Sample dependencies
- `../../Utilities/cuda_samples_utils.py` - Common utilities (imported by this sample)
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [`cuda.core` compilation API](https://nvidia.github.io/cuda-python/cuda-core/latest/api.html#cuda-compilation-toolchain)
- Upstream `cuda.core` example: [`jit_lto_fractal.py`](https://github.com/NVIDIA/cuda-python/blob/main/cuda_core/examples/jit_lto_fractal.py)
- [NVIDIA nvJitLink documentation](https://docs.nvidia.com/cuda/nvjitlink/index.html)

View File

@@ -0,0 +1,223 @@
# 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.
"""
JIT Compilation and Link-Time Optimization with cuda.core
Real-world GPU code is rarely a single source string. Libraries ship a
"main" kernel that is compiled once, then link in user-supplied device
functions at runtime to customize behavior without recompiling the whole
program.
cuda.core exposes this pattern through ``Program`` (NVRTC compilation)
and ``Linker`` (JIT linking of multiple object codes). Two modes are
shown here:
* **PTX linking**: compile each translation unit with
``relocatable_device_code=True`` to PTX and link to a CUBIN.
The two modules remain independently compiled: no cross-module
inlining.
* **LTO (Link-Time Optimization)**: compile each translation unit
with ``link_time_optimization=True`` to LTO IR, then link with
``LinkerOptions(link_time_optimization=True)``. The linker reruns
the optimizer across both modules and can inline the device function
into the main kernel, typically matching a single-source build.
The same kernel math runs in both modes and is verified against a
NumPy reference.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
try:
import cupy as cp
import numpy as np
from cuda.core import (
Device,
LaunchConfig,
Linker,
LinkerOptions,
Program,
ProgramOptions,
launch,
)
from cuda_samples_utils import print_gpu_info # noqa: E402
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
# --------------------------------------------------------------------------
# Module A: the "library" main kernel. It forwards each element through a
# user-supplied device function (resolved at link time) and writes the result.
# --------------------------------------------------------------------------
MAIN_SRC = r"""
// Forward declare the user-supplied hook. Its definition lives in a separate
// translation unit and is resolved by the Linker at runtime.
extern "C" __device__ float user_transform(float x);
extern "C" __global__
void apply_transform(const float* __restrict__ in,
float* __restrict__ out,
size_t N)
{
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t stride = (size_t)gridDim.x * blockDim.x;
for (size_t i = tid; i < N; i += stride) {
out[i] = user_transform(in[i]);
}
}
"""
# --------------------------------------------------------------------------
# Module B: the user-supplied "plug-in" device function. A different
# implementation of ``user_transform`` here produces different results without
# rebuilding MAIN_SRC.
# --------------------------------------------------------------------------
USER_SRC = r"""
extern "C" __device__
float user_transform(float x)
{
// A deliberately non-trivial expression so LTO has something to inline /
// optimize across the module boundary.
float y = x * x + 3.0f * x - 1.0f;
return y > 0.0f ? y : 0.0f;
}
"""
def host_reference(x: np.ndarray) -> np.ndarray:
y = x * x + 3.0 * x - 1.0
return np.where(y > 0.0, y, 0.0).astype(np.float32)
def link_ptx(device):
"""Compile both modules to PTX and link them into a cubin (no LTO)."""
prog_opts = ProgramOptions(
std="c++17", arch=f"sm_{device.arch}", relocatable_device_code=True
)
main_obj = Program(MAIN_SRC, "c++", options=prog_opts).compile("ptx")
user_obj = Program(USER_SRC, "c++", options=prog_opts).compile("ptx")
linker = Linker(main_obj, user_obj, options=LinkerOptions(arch=f"sm_{device.arch}"))
return linker.link("cubin")
def link_lto(device):
"""Compile both modules to LTO IR and link with LTO enabled."""
prog_opts = ProgramOptions(
std="c++17", arch=f"sm_{device.arch}", link_time_optimization=True
)
main_obj = Program(MAIN_SRC, "c++", options=prog_opts).compile("ltoir")
user_obj = Program(USER_SRC, "c++", options=prog_opts).compile("ltoir")
linker_opts = LinkerOptions(
arch=f"sm_{device.arch}", link_time_optimization=True
)
linker = Linker(main_obj, user_obj, options=linker_opts)
return linker.link("cubin")
def run_one_mode(mode, module, stream, d_in, d_out, size, expected):
kernel = module.get_kernel("apply_transform")
config = LaunchConfig(grid=(size + 255) // 256, block=256)
launch(
stream,
config,
kernel,
d_in.data.ptr,
d_out.data.ptr,
np.uint64(size),
)
stream.sync()
actual = cp.asnumpy(d_out)
if not np.allclose(actual, expected, rtol=1e-5, atol=1e-5):
max_err = np.max(np.abs(actual - expected))
print(f" [{mode}] verification FAILED (max_err={max_err})")
return False
print(f" [{mode}] result verified against NumPy reference")
return True
def main() -> int:
import argparse
parser = argparse.ArgumentParser(
description="JIT + LTO linking of two device modules with cuda.core"
)
parser.add_argument(
"--elements", type=int, default=1 << 16,
help="Number of float32 elements (default: 65536)",
)
parser.add_argument("--device", type=int, default=0, help="CUDA device id")
args = parser.parse_args()
device = Device(args.device)
device.set_current()
print_gpu_info(device)
stream = device.create_stream()
cp.cuda.ExternalStream(int(stream.handle)).use()
try:
N = args.elements
rng = np.random.default_rng(seed=0)
host_in = rng.standard_normal(N).astype(np.float32)
expected = host_reference(host_in)
d_in = cp.asarray(host_in)
d_out = cp.empty(N, dtype=cp.float32)
device.sync()
print("\n[1] PTX linking (no LTO)")
ptx_module = link_ptx(device)
ok_ptx = run_one_mode("ptx", ptx_module, stream, d_in, d_out, N, expected)
d_out.fill(0)
device.sync()
print("\n[2] LTO linking (link-time optimization)")
lto_module = link_lto(device)
ok_lto = run_one_mode("lto", lto_module, stream, d_in, d_out, N, expected)
print()
if ok_ptx and ok_lto:
print("Both PTX and LTO linked kernels produced matching results. Done")
return 0
return 1
finally:
stream.close()
cp.cuda.Stream.null.use()
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,5 @@
# JIT + LTO Linking Sample Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0

View File

@@ -0,0 +1,184 @@
# Sample: Launch Configuration Tuning (Python)
## Description
Benchmark different CUDA kernel launch configurations to find the optimal block-size setting using `cuda.core` APIs. This sample demonstrates **performance tuning** by measuring execution time across various thread block sizes.
## What You'll Learn
- Compiling CUDA kernels at runtime with `cuda.core.Program`
- Launching kernels with different `LaunchConfig` settings
- Benchmarking kernel performance with precise timing
- Understanding how thread block size affects performance
- Tuning for memory-bound vs compute-bound kernels
## Key Concepts
### Launch Configuration with cuda.core
```python
# Configure kernel launch with specific thread block size
config = LaunchConfig(
grid=(grid_size,),
block=(block_size,),
shmem_size=shared_memory_bytes
)
# Launch kernel
launch(stream, config, kernel, *args)
stream.sync()
```
### Thread Block Sizing
Thread block size significantly impacts performance due to:
| Factor | Impact |
|--------|--------|
| **Occupancy** | More active warps can hide memory latency |
| **Registers** | More threads/block = fewer registers/thread |
| **Shared Memory** | Divided among blocks on each SM |
| **Warp Efficiency** | Block size should be multiple of 32 |
### Benchmarking Approach
```python
# Use CUDA events for accurate GPU timing (not CPU wall-clock)
start_event = device.create_event(options=EventOptions(enable_timing=True))
end_event = device.create_event(options=EventOptions(enable_timing=True))
stream.record(start_event)
for _ in range(n_iterations):
launch(stream, config, kernel, *args)
stream.record(end_event)
end_event.sync()
elapsed_ms = (end_event - start_event) / n_iterations
```
## Key APIs
### From `cuda.core`:
- `Device` - CUDA device management
- `Program` - Runtime kernel compilation (NVRTC)
- `ProgramOptions` - Compilation options (architecture target)
- `LaunchConfig` - Kernel launch configuration (grid/block dimensions)
- `launch` - Execute compiled kernel (accepts Buffer objects directly)
- `EventOptions` - GPU timing with CUDA events
- `ManagedMemoryResource` - Device-preferred unified memory
- `ManagedMemoryResourceOptions` - Set preferred_location for representative benchmarks
### From `numpy`:
- `np.from_dlpack()` - Zero-copy view of GPU buffers via DLPack
### Benchmarked Kernels:
- **vector_add** - Simple memory-bound kernel (C = A + B) - low sensitivity to block size
- **reduce_sum** - Shared memory reduction - high sensitivity to block size
## Requirements
### Hardware:
- NVIDIA GPU with CUDA support
- Minimum GPU memory: 512 MB
### Software:
- CUDA Toolkit 13.0 or newer
- Python 3.10 or newer
- See `requirements.txt` for Python packages
## Installation
```bash
pip install -r requirements.txt
```
## How to Run
```bash
python launchConfigTuning.py
```
## Expected Output
```
============================================================
Launch Configuration Tuning (cuda.core)
Finding the Best Block Size for Your Kernel
============================================================
Device: <Your GPU Name>
Compute Capability: X.X
Compiling CUDA kernels with cuda.core.Program...
Target architecture: sm_XX
✓ vector_add kernel compiled
✓ reduce_sum kernel compiled
============================================================
VECTOR ADDITION - Launch Configuration Tuning
============================================================
Problem size: 10,000,000 elements
Kernel: vector_add (C = A + B)
Testing thread configurations: [32, 64, 128, 256, 512, 1024]
------------------------------------------------------------
Block Size: 32 | Blocks: 312500 | Time: X.XXXX ± X.XXXX ms
Block Size: 64 | Blocks: 156250 | Time: X.XXXX ± X.XXXX ms
...
------------------------------------------------------------
✓ OPTIMAL: block_size=XXX (X.XXXX ms)
✗ WORST: block_size=XXX (X.XXXX ms)
Speedup: X.XXx
✓ Results verified correct!
...
============================================================
SAMPLE COMPLETE
============================================================
Key Takeaway: The optimal thread configuration depends on your
specific kernel characteristics. Always benchmark to find the best!
```
## Tuning Guidelines
### Start Here
- **128-256 threads/block** is a good starting point for most kernels
- Always use **multiples of 32** (warp size)
### Memory-Bound Kernels
- Less sensitive to thread configuration
- Focus on memory access patterns
- Higher thread counts help hide latency
### Compute-Bound Kernels
- More sensitive to thread configuration
- Watch for register pressure at high thread counts
- Profile with Nsight Compute
### Reduction Kernels
- Block size affects shared memory usage
- Power-of-2 sizes simplify reduction logic
- Often 256-512 threads works well
## Files
- `launchConfigTuning.py` - Python implementation using cuda.core
- `README.md` - This file
- `requirements.txt` - Sample dependencies
## See Also
- [cuda.core Documentation](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- [cuda.core.LaunchConfig](https://nvidia.github.io/cuda-python/cuda-core/latest/generated/cuda.core.LaunchConfig.html)
- [CUDA Occupancy Calculator](https://docs.nvidia.com/cuda/cuda-occupancy-calculator/)
- [CUDA Best Practices Guide - Execution Configuration](https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html#execution-configuration-optimizations)
- [Nsight Compute Profiler](https://developer.nvidia.com/nsight-compute)

View File

@@ -0,0 +1,388 @@
# 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.
"""
Launch Configuration Tuning
Demonstrates how to find the optimal threads-per-block configuration for CUDA
kernels using cuda.core APIs. Benchmarks different thread layouts to answer:
"What is the best threads-per-block for my kernel?"
"""
import sys
try:
import numpy as np
from cuda.core import (
Device,
EventOptions,
LaunchConfig,
ManagedMemoryResource,
ManagedMemoryResourceOptions,
Program,
ProgramOptions,
launch,
)
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
# =============================================================================
# CUDA Kernel Source Code
# =============================================================================
# Vector Addition Kernel - Simple memory-bound kernel (grid-stride loop)
VECTOR_ADD_KERNEL = r"""
extern "C" __global__
void vector_add(const float* __restrict__ a,
const float* __restrict__ b,
float* __restrict__ c,
int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int stride = blockDim.x * gridDim.x;
for (int i = idx; i < n; i += stride) {
c[i] = a[i] + b[i];
}
}
"""
# Reduction Kernel - Sensitive to block size due to shared memory (grid-stride load)
REDUCTION_KERNEL = r"""
extern "C" __global__
void reduce_sum(const float* __restrict__ input,
float* __restrict__ partial_sums,
int n) {
extern __shared__ float sdata[];
unsigned int tid = threadIdx.x;
unsigned int stride = blockDim.x * gridDim.x;
// Load data into shared memory (grid-stride loop)
float sum = 0.0f;
for (unsigned int i = blockIdx.x * blockDim.x + tid; i < n; i += stride) {
sum += input[i];
}
sdata[tid] = sum;
__syncthreads();
// Perform reduction in shared memory
for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
if (tid < s) {
sdata[tid] += sdata[tid + s];
}
__syncthreads();
}
// Write result for this block
if (tid == 0) {
partial_sums[blockIdx.x] = sdata[0];
}
}
"""
# =============================================================================
# Utility Functions
# =============================================================================
def compile_kernel(device, kernel_code, kernel_name):
"""Compile a CUDA kernel using cuda.core.Program."""
arch = f"sm_{device.arch}"
options = ProgramOptions(arch=arch)
program = Program(kernel_code, code_type="c++", options=options)
compiled = program.compile(target_type="cubin")
return compiled.get_kernel(kernel_name)
def benchmark_kernel_1d(
device,
stream,
kernel,
args,
n_elements,
block_size,
n_iterations=100,
shared_mem_bytes=0,
):
"""
Benchmark a 1D kernel with given threads-per-block configuration.
Uses CUDA events for accurate GPU timing.
Returns timing statistics as a dictionary.
"""
grid_size = (n_elements + block_size - 1) // block_size
config = LaunchConfig(
grid=(grid_size,), block=(block_size,), shmem_size=shared_mem_bytes
)
# Warm-up run
launch(stream, config, kernel, *args)
stream.sync()
# Timed runs with CUDA events
event_opts = EventOptions(enable_timing=True)
start_event = device.create_event(options=event_opts)
end_event = device.create_event(options=event_opts)
stream.record(start_event)
for _ in range(n_iterations):
launch(stream, config, kernel, *args)
stream.record(end_event)
end_event.sync()
elapsed_ms = (end_event - start_event) / n_iterations
return {
"block_size": block_size,
"grid_size": grid_size,
"mean_time_ms": elapsed_ms,
"std_time_ms": 0.0, # Single measurement with events
}
def print_gpu_info(device):
"""Print GPU information relevant to launch configuration."""
print(f"\nDevice: {device.name}")
cc = device.compute_capability
print(f"Compute Capability: {cc.major}.{cc.minor}")
def allocate_managed_array(mr, stream, n_elements, dtype=np.float32):
"""Allocate device-preferred unified memory and return buffer with numpy view."""
n_bytes = n_elements * np.dtype(dtype).itemsize
buffer = mr.allocate(n_bytes, stream)
stream.sync()
# Zero-copy numpy view via DLPack (holds reference to buffer)
np_view = np.from_dlpack(buffer).view(dtype).reshape(n_elements)
return buffer, np_view
# =============================================================================
# Benchmark Demonstrations
# =============================================================================
def demo_vector_add_tuning(device, stream, mr, kernel):
"""Demonstrate launch configuration tuning for vector addition."""
print("\n" + "=" * 60)
print("VECTOR ADDITION - Launch Configuration Tuning")
print("=" * 60)
N = 10_000_000 # 10 million elements
print(f"\nProblem size: {N:,} elements")
print("Kernel: vector_add (C = A + B)")
# Allocate device-preferred unified memory via cuda.core
d_a, np_a = allocate_managed_array(mr, stream, N)
d_b, np_b = allocate_managed_array(mr, stream, N)
d_c, np_c = allocate_managed_array(mr, stream, N)
try:
# Initialize data via numpy views
np_a[:] = np.random.rand(N).astype(np.float32)
np_b[:] = np.random.rand(N).astype(np.float32)
stream.sync()
# Thread configurations to test (multiples of warp size = 32)
thread_configs = [32, 64, 128, 256, 512, 1024]
print(f"\nTesting thread configurations: {thread_configs}")
print("-" * 60)
results = []
for tpb in thread_configs:
result = benchmark_kernel_1d(
device,
stream,
kernel,
(d_a, d_b, d_c, np.int32(N)),
N,
tpb,
n_iterations=100,
)
results.append(result)
print(
f"Block Size: {tpb:4d} | Blocks: {result['grid_size']:6d} | "
f"Time: {result['mean_time_ms']:.4f} ms"
)
# Find optimal and worst configurations
best = min(results, key=lambda x: x["mean_time_ms"])
worst = max(results, key=lambda x: x["mean_time_ms"])
print("-" * 60)
print(
f"\n✓ OPTIMAL: block_size={best['block_size']} "
f"({best['mean_time_ms']:.4f} ms)"
)
print(
f"✗ WORST: block_size={worst['block_size']} "
f"({worst['mean_time_ms']:.4f} ms)"
)
print(f" Speedup: {worst['mean_time_ms']/best['mean_time_ms']:.2f}x")
# Verify result
stream.sync()
expected = np_a + np_b
if np.allclose(np_c, expected):
print("\n✓ Results verified correct!")
return results
finally:
d_a.close()
d_b.close()
d_c.close()
def demo_reduction_tuning(device, stream, mr, kernel):
"""Demonstrate launch config tuning for reduction (shared memory)."""
print("\n" + "=" * 60)
print("REDUCTION - Launch Configuration Tuning")
print("=" * 60)
N = 16_777_216 # 16M elements (power of 2)
print(f"\nProblem size: {N:,} elements")
print("Kernel: reduce_sum (parallel reduction)")
print("Note: Reduction uses shared memory - more sensitive to block size!")
# Allocate device-preferred unified memory via cuda.core
d_input, np_input = allocate_managed_array(mr, stream, N)
try:
np_input[:] = np.random.rand(N).astype(np.float32)
stream.sync()
thread_configs = [32, 64, 128, 256, 512, 1024]
print(f"\nTesting thread configurations: {thread_configs}")
print("-" * 60)
results = []
for tpb in thread_configs:
# Allocate partial sums array
n_blocks = (N + tpb - 1) // tpb
d_partial, _ = allocate_managed_array(mr, stream, n_blocks)
try:
# Shared memory size = block_size * sizeof(float)
shared_mem_bytes = tpb * 4
result = benchmark_kernel_1d(
device,
stream,
kernel,
(d_input, d_partial, np.int32(N)),
N,
tpb,
n_iterations=50,
shared_mem_bytes=shared_mem_bytes,
)
results.append(result)
print(
f"Block Size: {tpb:4d} | Blocks: {result['grid_size']:6d} | "
f"Time: {result['mean_time_ms']:.4f} ms"
)
finally:
d_partial.close()
best = min(results, key=lambda x: x["mean_time_ms"])
worst = max(results, key=lambda x: x["mean_time_ms"])
print("-" * 60)
print(f"\n✓ OPTIMAL: block_size={best['block_size']}")
print(
f" Speedup over worst: {worst['mean_time_ms']/best['mean_time_ms']:.2f}x"
)
return results
finally:
d_input.close()
# =============================================================================
# Main
# =============================================================================
def main():
"""
Complete demonstration of CUDA launch configuration tuning.
This sample shows:
1. Device initialization with cuda.core.Device
2. Kernel compilation with cuda.core.Program
3. Benchmarking different thread block configurations
4. Finding optimal threads-per-block for various kernel types
"""
print("=" * 60)
print("Launch Configuration Tuning (cuda.core)")
print("Finding the Best Block Size for Your Kernel")
print("=" * 60)
# Initialize CUDA device
device = Device(0)
device.set_current()
# Print GPU information
print_gpu_info(device)
# Create stream and device-preferred memory resource
stream = device.create_stream()
mr_options = ManagedMemoryResourceOptions(preferred_location=device.device_id)
mr = ManagedMemoryResource(mr_options)
try:
# Compile kernels
print("\nCompiling CUDA kernels with cuda.core.Program...")
arch = f"sm_{device.arch}"
print(f" Target architecture: {arch}")
vec_add_kernel = compile_kernel(device, VECTOR_ADD_KERNEL, "vector_add")
print(" ✓ vector_add kernel compiled")
reduction_kernel = compile_kernel(device, REDUCTION_KERNEL, "reduce_sum")
print(" ✓ reduce_sum kernel compiled")
# Run demonstrations
demo_vector_add_tuning(device, stream, mr, vec_add_kernel)
demo_reduction_tuning(device, stream, mr, reduction_kernel)
print("\n" + "=" * 60)
print("SAMPLE COMPLETE")
print("=" * 60)
print("\nKey Takeaway: The optimal thread configuration depends on your")
print("specific kernel characteristics. Always benchmark to find the best!")
print()
finally:
stream.close()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,6 @@
# Launch Configuration Tuning Sample Requirements
# Requires Python 3.10+, CUDA Toolkit 13.0+
cuda-python>=13.0.0
cuda-core>=0.6.0
numpy>=2.3.2

View File

@@ -0,0 +1,163 @@
# Matrix Multiplication with Shared Memory (GEMM)
Demonstrates efficient matrix multiplication using nvmath-python APIs and custom CUDA kernels with tiling, shared memory, and loop unrolling.
## Overview
- Uses nvmath.linalg.advanced.Matmul for high-performance GEMM via cuBLASLt
- Compares with custom CUDA kernel using tiling and shared memory
- Shows how tiling reduces global memory bandwidth requirements
- Demonstrates shared memory for data reuse within thread blocks
- Uses loop unrolling to improve instruction-level parallelism
## What You'll Learn
- How to use nvmath stateful API for optimized matrix multiplication
- How to tile matrix operations for better cache locality
- Using shared memory to reduce redundant global memory accesses
- Loop unrolling techniques for GPU kernels
- Benchmarking and comparing kernel performance
## Key Libraries
- `nvmath-python` - NVIDIA math library with cuBLASLt access
- `cuda.core` - Modern CUDA Python API for custom kernel compilation
- `cupy` - GPU array library for Python
## Key APIs
### From `nvmath.linalg.advanced`:
- `Matmul()` - Stateful matrix multiplication with planning and execution phases
- `MatmulComputeType` - Compute type options for mixed-precision
### From `cuda.core`:
- `Device()` - CUDA device management and properties
- `Program()` - Runtime kernel compilation (NVRTC)
- `LaunchConfig()` - Kernel launch configuration (grid/block dimensions)
- `launch()` - Kernel execution on a stream
- `Stream.record_event()` / `Event.elapsed_time()` - GPU timing
## Requirements
### Hardware:
- NVIDIA GPU with Compute Capability 7.0 or higher
- Minimum GPU memory: 256 MB (for 1024×1024 matrices)
### Software:
- CUDA Toolkit 13.0 or newer
- Python 3.10 or newer
- See requirements.txt for package dependencies
## Installation
```bash
cd cuda-samples/python/2_CoreConcepts/matrixMulSharedMem
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
## How to Run
```bash
python matrixMulSharedMem.py
```
## Expected Output
```
======================================================================
Matrix Multiplication with Shared Memory (GEMM)
Using nvmath and cuda.core APIs
======================================================================
Device: NVIDIA GeForce RTX 4090
Compute Capability: sm_89
Custom kernel compiled ✓
Matrix dimensions: A(1024x1024) × B(1024x1024) = C(1024x1024)
Custom kernel tile size: 16x16
----------------------------------------------------------------------
NVMATH MATMUL (cuBLASLt)
----------------------------------------------------------------------
Using nvmath.linalg.advanced.Matmul stateful API
Average time: X.XXX ms
Performance: XXXX.XX GFLOPS
----------------------------------------------------------------------
CUSTOM KERNEL (Tiled + Shared Memory + Loop Unrolling)
----------------------------------------------------------------------
Grid: (64, 64), Block: (16, 16)
Average time: X.XXX ms
Performance: XXX.XX GFLOPS
----------------------------------------------------------------------
VERIFICATION
----------------------------------------------------------------------
nvmath : PASSED (max error: X.XXe-XX)
Custom kernel : PASSED (max error: X.XXe-XX)
======================================================================
PERFORMANCE SUMMARY
======================================================================
Implementation Time (ms) GFLOPS
----------------------------------------------------------------------
nvmath (cuBLASLt) X.XXX XXXX.XX
Custom (shared mem + unroll) X.XXX XXX.XX
```
## Tiling Concept
```
Matrix A (M×K) Matrix B (K×N) Matrix C (M×N)
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ T00 │ T01 │...│ │ T00 │ T01 │...│ │ │ │ │
├─────┼─────┼───┤ ├─────┼─────┼───┤ ├─────┼─────┼───┤
│ T10 │ T11 │...│ × │ T10 │ T11 │...│ = │ │ Cij │ │
├─────┼─────┼───┤ ├─────┼─────┼───┤ ├─────┼─────┼───┤
│ ... │ ... │...│ │ ... │ ... │...│ │ │ │ │
└───────────────┘ └───────────────┘ └───────────────┘
Cij = Σ (A_tile_row × B_tile_col) for all tiles along K
```
## nvmath Stateful API
```python
import nvmath.linalg.advanced as nvmath_advanced
# Create matrices (CuPy arrays)
A = cp.random.rand(m, k).astype(cp.float32)
B = cp.random.rand(k, n).astype(cp.float32)
# Use stateful API for fine-grained control
with nvmath_advanced.Matmul(A, B) as mm:
mm.plan() # Find optimal algorithm
C = mm.execute() # Execute computation
```
## Memory Access Optimization (Custom Kernel)
| Implementation | Global Reads per C element | Reduction |
|---------------|---------------------------|-----------|
| Naive | 2 × K | (baseline)|
| Tiled (16×16) | 2 × K / 16 | 16× |
## Files
- `matrixMulSharedMem.py` - Python implementation comparing nvmath vs custom kernel
- `README.md` - This file
- `requirements.txt` - Sample dependencies
## See Also
- [nvmath-python Documentation](https://docs.nvidia.com/cuda/nvmath-python/)
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [cuda.core API Guide](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- [CuPy Documentation](https://docs.cupy.dev/)

View File

@@ -0,0 +1,243 @@
# 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.
"""
Matrix Multiplication with Shared Memory (GEMM)
Demonstrates efficient matrix multiplication using:
- nvmath.linalg.advanced.Matmul for high-performance GEMM via cuBLASLt
- Custom CUDA kernel with tiling, shared memory, and loop unrolling
Uses cuda.core APIs with CuPy arrays via ExternalStream.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
try:
import cupy as cp
import numpy as np
import nvmath.linalg.advanced as nvmath_advanced
from cuda.core import (
Device,
EventOptions,
LaunchConfig,
Program,
ProgramOptions,
launch,
)
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Install with: pip install -r requirements.txt")
sys.exit(1)
TILE_SIZE: int = 16
MATMUL_KERNEL: str = r"""
#define TILE_SIZE 16
extern "C" __global__
void matmul_shared(const float* A, const float* B, float* C,
int M, int N, int K) {
__shared__ float As[TILE_SIZE][TILE_SIZE];
__shared__ float Bs[TILE_SIZE][TILE_SIZE];
int bx = blockIdx.x, by = blockIdx.y;
int tx = threadIdx.x, ty = threadIdx.y;
int row = by * TILE_SIZE + ty;
int col = bx * TILE_SIZE + tx;
float sum = 0.0f;
int numTiles = (K + TILE_SIZE - 1) / TILE_SIZE;
for (int t = 0; t < numTiles; t++) {
int aCol = t * TILE_SIZE + tx;
int bRow = t * TILE_SIZE + ty;
As[ty][tx] = (row < M && aCol < K) ? A[row * K + aCol] : 0.0f;
Bs[ty][tx] = (bRow < K && col < N) ? B[bRow * N + col] : 0.0f;
__syncthreads();
#pragma unroll
for (int k = 0; k < TILE_SIZE; k += 4) {
sum += As[ty][k] * Bs[k][tx];
sum += As[ty][k + 1] * Bs[k + 1][tx];
sum += As[ty][k + 2] * Bs[k + 2][tx];
sum += As[ty][k + 3] * Bs[k + 3][tx];
}
__syncthreads();
}
if (row < M && col < N) {
C[row * N + col] = sum;
}
}
"""
def run_matmul_benchmark(
m: int = 1024,
n: int = 1024,
k: int = 1024,
device_id: int = 0,
num_iterations: int = 10,
) -> bool:
"""Run matrix multiplication benchmark comparing nvmath vs custom kernel."""
print("=" * 60)
print("Matrix Multiplication with Shared Memory (GEMM)")
print("=" * 60)
# Initialize device and stream
device = Device(device_id)
device.set_current()
stream = device.create_stream()
print(f"\nDevice: {device.name}")
print(f"Compute Capability: sm_{device.arch}")
# Make CuPy use our cuda.core stream
cp.cuda.ExternalStream(int(stream.handle)).use()
# Compile custom kernel
arch = f"sm_{device.arch}"
program = Program(MATMUL_KERNEL, code_type="c++", options=ProgramOptions(arch=arch))
kernel = program.compile(target_type="cubin").get_kernel("matmul_shared")
print("Custom kernel compiled ✓")
# Setup
print(f"\nMatrix: A({m}x{k}) × B({k}x{n}) = C({m}x{n})")
total_ops = 2 * m * n * k
event_opts = EventOptions(enable_timing=True)
# Allocate matrices
rng = cp.random.default_rng(42)
d_A = rng.random((m, k), dtype=cp.float32)
d_B = rng.random((k, n), dtype=cp.float32)
d_C_custom = cp.zeros((m, n), dtype=cp.float32)
success = True
try:
# -------------------------------------------------------------------------
# nvmath GEMM (cuBLASLt)
# -------------------------------------------------------------------------
print("\n" + "-" * 60)
print("NVMATH (cuBLASLt) - plan once, execute many")
print("-" * 60)
with nvmath_advanced.Matmul(d_A, d_B, stream=int(stream.handle)) as mm:
mm.plan()
d_C_nvmath = mm.execute()
stream.sync()
start = stream.record(options=event_opts)
for _ in range(num_iterations):
d_C_nvmath = mm.execute()
end = stream.record(options=event_opts)
end.sync()
nvmath_ms = (end - start) / num_iterations
nvmath_gflops = (total_ops / 1e9) / (nvmath_ms / 1e3)
print(f"Time: {nvmath_ms:.3f} ms | {nvmath_gflops:.2f} GFLOPS")
# -------------------------------------------------------------------------
# Custom kernel (tiled + shared memory + unroll)
# -------------------------------------------------------------------------
print("\n" + "-" * 60)
print("CUSTOM KERNEL (tiled + shared memory + unroll)")
print("-" * 60)
block = (TILE_SIZE, TILE_SIZE)
grid = ((n + TILE_SIZE - 1) // TILE_SIZE, (m + TILE_SIZE - 1) // TILE_SIZE)
config = LaunchConfig(grid=grid, block=block)
launch(
stream,
config,
kernel,
d_A.data.ptr,
d_B.data.ptr,
d_C_custom.data.ptr,
np.int32(m),
np.int32(n),
np.int32(k),
)
stream.sync()
start = stream.record(options=event_opts)
for _ in range(num_iterations):
launch(
stream,
config,
kernel,
d_A.data.ptr,
d_B.data.ptr,
d_C_custom.data.ptr,
np.int32(m),
np.int32(n),
np.int32(k),
)
end = stream.record(options=event_opts)
end.sync()
custom_ms = (end - start) / num_iterations
custom_gflops = (total_ops / 1e9) / (custom_ms / 1e3)
print(f"Time: {custom_ms:.3f} ms | {custom_gflops:.2f} GFLOPS")
# -------------------------------------------------------------------------
# Verification
# -------------------------------------------------------------------------
print("\n" + "-" * 60)
print("VERIFICATION")
print("-" * 60)
d_C_ref = d_A @ d_B
# Host-side verification: cp.allclose triggers NVRTC failure on sm_120
# (ldexp_cexp undefined). Use asnumpy + np.allclose instead.
ref_host = cp.asnumpy(d_C_ref)
for name, d_C in [("nvmath", d_C_nvmath), ("custom", d_C_custom)]:
print(f"{name}: ", end="")
passed = np.allclose(cp.asnumpy(d_C), ref_host, rtol=1e-4, atol=1e-4)
print("Test PASSED" if passed else "Test FAILED")
success = success and passed
return success
finally:
cp.cuda.Stream.null.use()
stream.close()
def main() -> bool:
"""Entry point. Returns True if benchmark passed."""
return run_matmul_benchmark()
if __name__ == "__main__":
success = main()
if not success:
sys.exit(1)

View File

@@ -0,0 +1,7 @@
# Matrix Multiplication with Shared Memory (GEMM) Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0
numpy>=2.3.2
nvmath-python[cu13]>=0.3.0

View File

@@ -0,0 +1,139 @@
# Sample: Memory Resources and Buffers (Python)
## Description
This sample demonstrates the `cuda.core` memory management model: a
`MemoryResource` owns a pool of memory and hands out `Buffer` objects that
can be passed to kernels, copied between resources with
`Buffer.copy_to()`, and viewed as NumPy or CuPy arrays through DLPack. The
script exercises three common resources side-by-side:
1. **`DeviceMemoryResource`** - device-local GPU memory. Every `Device`
exposes a default pool via `Device.memory_resource`, and applications
can create additional pools explicitly.
2. **`PinnedMemoryResource`** - page-locked host memory, used here as the
input and output staging buffers around a GPU kernel (the canonical
pinned-H2D / compute / pinned-D2H pattern).
3. **`ManagedMemoryResource`** - unified memory that the driver migrates
between host and device on demand; host views see the GPU's writes
without an explicit copy.
The same `scale_and_bias` kernel runs on each resource and every result is
verified on the host.
## What You'll Learn
- Creating and using `DeviceMemoryResource`, `PinnedMemoryResource`, and
`ManagedMemoryResource`
- Allocating `Buffer` objects from a resource with a bound stream
- Copying between buffers across resources with `Buffer.copy_to()`
- Taking zero-copy NumPy or CuPy views of a `Buffer` via DLPack
- Releasing buffers with stream-ordered `close(stream)` semantics
## Key Libraries
- [`cuda.core`](https://nvidia.github.io/cuda-python/cuda-core/latest/) - Pythonic access to CUDA runtime, programs, and memory resources
- `cupy` - GPU array views of device buffers
- `numpy` - host array views of pinned and managed buffers
## Key APIs
### From `cuda.core`
- `Device.memory_resource` - default memory pool attached to a device
- `DeviceMemoryResource`, `PinnedMemoryResource`, `ManagedMemoryResource` - allocate buffers of the corresponding memory kind
- `MemoryResource.allocate(nbytes, stream=...)` - returns a `Buffer`
- `Buffer.copy_to(dst_buffer, stream=...)` - async, stream-ordered copy
- `Buffer.close(stream)` - stream-ordered deallocation
- `Buffer` supports `__dlpack__` for zero-copy views
### From CuPy and NumPy
- `cp.from_dlpack()` / `np.from_dlpack()` - zero-copy array view of a `Buffer`
### From `cuda_samples_utils`
- `print_gpu_info()` - print device name and compute capability
## Requirements
### Hardware
- NVIDIA GPU with Compute Capability 7.0 or higher
- Managed memory support (most discrete GPUs on Linux and Windows)
### Software
- CUDA Toolkit 13.0 or newer (matches `cuda-python` 13.x)
- Python 3.10 or newer
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## Installation
Install the required packages from `requirements.txt`:
```bash
cd /path/to/cuda-samples/python/2_CoreConcepts/memoryResources
pip install -r requirements.txt
```
The `requirements.txt` installs:
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## How to Run
### Basic usage
```bash
cd cuda-samples/python/2_CoreConcepts/memoryResources
python memoryResources.py
```
### With custom parameters
```bash
# Larger buffer size
python memoryResources.py --elements 1048576
# Use a specific GPU
python memoryResources.py --device 1
```
## Expected Output
```
Device: <Your GPU Name>
Compute Capability: <X.Y>
[1] DeviceMemoryResource + PinnedMemoryResource (staging)
Pinned staging, device kernel, and copy_to verified
[2] ManagedMemoryResource (unified memory)
GPU writes observed directly through the host-visible mapping
[3] Explicit DeviceMemoryResource
Explicit DeviceMemoryResource allocation verified
All memory resource demos passed.
```
**Note:** Device name and compute capability will vary based on your GPU.
## Files
- `memoryResources.py` - Python implementation using `cuda.core` memory resources
- `README.md` - This file
- `requirements.txt` - Sample dependencies
- `../../Utilities/cuda_samples_utils.py` - Common utilities (imported by this sample)
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [`cuda.core` memory API](https://nvidia.github.io/cuda-python/cuda-core/latest/api.html#memory-management)
- Upstream `cuda.core` example: [`memory_ops.py`](https://github.com/NVIDIA/cuda-python/blob/main/cuda_core/examples/memory_ops.py)
- Upstream `cuda.core` example: [`memory_pool_resources.py`](https://github.com/NVIDIA/cuda-python/blob/main/cuda_core/examples/memory_pool_resources.py)

View File

@@ -0,0 +1,248 @@
# 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.
"""
Memory management with cuda.core: Buffers and Memory Resources
Demonstrates the Memory Resource / Buffer abstraction in cuda.core:
* ``DeviceMemoryResource`` - GPU-only memory (device pool)
* ``PinnedMemoryResource`` - page-locked host memory accessible by the GPU
* ``ManagedMemoryResource`` - unified memory that migrates between
host and device on demand
Each resource hands out ``Buffer`` objects that can be:
* passed to kernels as pointers
* copied between each other with ``buffer.copy_to(...)``
* viewed as NumPy or CuPy arrays via DLPack (``__dlpack__``)
The kernel below performs a fused scale + bias on both a device buffer
and a pinned buffer, then we copy the result across resources to confirm
each pathway works end-to-end.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
try:
import cupy as cp
import numpy as np
from cuda.core import (
Device,
DeviceMemoryResource,
LaunchConfig,
ManagedMemoryResource,
PinnedMemoryResource,
Program,
ProgramOptions,
launch,
)
from cuda_samples_utils import print_gpu_info # noqa: E402
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
SCALE_BIAS_KERNEL = r"""
extern "C" __global__
void scale_and_bias(float* data, size_t N, float scale, float bias) {
const unsigned int tid = threadIdx.x + blockIdx.x * blockDim.x;
const unsigned int stride = blockDim.x * gridDim.x;
for (size_t i = tid; i < N; i += stride) {
data[i] = data[i] * scale + bias;
}
}
"""
def demo_device_and_pinned(device, stream, kernel, size):
"""Use pinned host memory as a staging area for a device-side kernel.
Canonical H2D / compute / D2H pattern:
host (pinned) -> device -> launch -> device -> host (pinned)
"""
print("\n[1] DeviceMemoryResource + PinnedMemoryResource (staging)")
dtype = np.float32
nbytes = size * dtype().itemsize
# The device's built-in memory resource is a good default for GPU memory.
device_mr = device.memory_resource
pinned_mr = PinnedMemoryResource()
pinned_in = pinned_mr.allocate(nbytes, stream=stream)
pinned_out = pinned_mr.allocate(nbytes, stream=stream)
device_buffer = device_mr.allocate(nbytes, stream=stream)
try:
# Wrap each Buffer as a typed array via DLPack (no copies).
pinned_in_view = np.from_dlpack(pinned_in).view(dtype=dtype)
pinned_out_view = np.from_dlpack(pinned_out).view(dtype=dtype)
# Initialize host-side input.
pinned_in_view[:] = np.arange(size, dtype=dtype)
original = pinned_in_view.copy()
# Stage H2D: pinned -> device.
pinned_in.copy_to(device_buffer, stream=stream)
# Launch kernel on the device buffer.
config = LaunchConfig(grid=(size + 255) // 256, block=256)
launch(
stream,
config,
kernel,
device_buffer,
np.uint64(size),
np.float32(3.0),
np.float32(-0.5),
)
# Stage D2H: device -> pinned.
device_buffer.copy_to(pinned_out, stream=stream)
stream.sync()
expected = original * 3.0 - 0.5
assert np.allclose(pinned_out_view, expected), "H2D -> kernel -> D2H mismatch"
print(" Pinned staging, device kernel, and copy_to verified")
finally:
device_buffer.close(stream)
pinned_out.close(stream)
pinned_in.close(stream)
def demo_managed(device, stream, kernel, size):
"""Allocate a managed (unified) buffer; kernel writes are visible on host."""
print("\n[2] ManagedMemoryResource (unified memory)")
dtype = np.float32
nbytes = size * dtype().itemsize
managed_mr = ManagedMemoryResource()
managed_buffer = managed_mr.allocate(nbytes, stream=stream)
try:
managed_view = np.from_dlpack(managed_buffer).view(dtype=dtype)
managed_view[:] = np.arange(size, dtype=dtype)
original = managed_view.copy()
# Before launching, make sure host writes have reached the GPU.
device.sync()
config = LaunchConfig(grid=(size + 255) // 256, block=256)
launch(
stream,
config,
kernel,
managed_buffer,
np.uint64(size),
np.float32(0.5),
np.float32(10.0),
)
stream.sync()
# No explicit copy: the same numpy view observes the GPU's writes.
assert np.allclose(managed_view, original * 0.5 + 10.0), (
"Managed memory result mismatch"
)
print(" GPU writes observed directly through the host-visible mapping")
finally:
managed_buffer.close(stream)
def demo_explicit_device_pool(device, stream, kernel, size):
"""Allocate from a user-created DeviceMemoryResource with default options."""
print("\n[3] Explicit DeviceMemoryResource")
dtype = np.float32
nbytes = size * dtype().itemsize
# Explicitly create a pool tied to this device. Use .close() to tear it down.
explicit_mr = DeviceMemoryResource(device)
buffer = explicit_mr.allocate(nbytes, stream=stream)
try:
view = cp.from_dlpack(buffer).view(dtype=cp.float32)
view[:] = cp.arange(size, dtype=cp.float32)
device.sync()
config = LaunchConfig(grid=(size + 255) // 256, block=256)
launch(
stream,
config,
kernel,
buffer,
np.uint64(size),
np.float32(1.0),
np.float32(100.0),
)
stream.sync()
expected = cp.arange(size, dtype=cp.float32) + 100.0
assert cp.allclose(view, expected), "Explicit device pool result mismatch"
print(" Explicit DeviceMemoryResource allocation verified")
finally:
buffer.close(stream)
explicit_mr.close()
def main():
import argparse
parser = argparse.ArgumentParser(
description="Demonstrate cuda.core memory resources (Buffer + MR)"
)
parser.add_argument(
"--elements",
type=int,
default=1 << 16,
help="Number of float32 elements per buffer (default: 65536)",
)
parser.add_argument("--device", type=int, default=0, help="CUDA device id")
args = parser.parse_args()
device = Device(args.device)
device.set_current()
print_gpu_info(device)
stream = device.create_stream()
try:
program_options = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
program = Program(SCALE_BIAS_KERNEL, code_type="c++", options=program_options)
module = program.compile("cubin")
kernel = module.get_kernel("scale_and_bias")
demo_device_and_pinned(device, stream, kernel, args.elements)
demo_managed(device, stream, kernel, args.elements)
demo_explicit_device_pool(device, stream, kernel, args.elements)
print("\nDone")
return 0
finally:
stream.close()
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,5 @@
# Memory Resources Sample Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0

View File

@@ -0,0 +1,158 @@
# Sample: PageRank Algorithm (Python)
## Description
Demonstrates GPU-accelerated PageRank computation for graph analysis using RAPIDS cuGraph, with cuda.core for device, stream, and GPU timing. This sample focuses on cuda.core integration with high-level libraries (cuGraph/cuDF); for custom kernel programming (Program, LaunchConfig, launch), see the blockwiseSum sample.
## What You'll Learn
- Graph representation using cuDF DataFrames for edge lists
- GPU-optimized PageRank via RAPIDS cuGraph library
- Performance comparison between cuGraph GPU and CPU reference implementation
- cuda.core device/stream management and GPU timing
## Key Libraries
- `cugraph` - RAPIDS GPU-accelerated graph analytics
- `cudf` - RAPIDS GPU DataFrame library
- `cuda.core` - Device, stream, and event APIs for GPU timing
- `cupy` - GPU array library (ExternalStream for cuDF/cuGraph)
- `numpy` - CPU reference implementation
## Key APIs
### From cuda.core:
- `Device(0)` - Create device, `device.set_current()`, `device.create_stream()`
- `EventOptions(enable_timing=True)` - GPU timing via `stream.record()`
- `cp.cuda.ExternalStream(stream.handle).use()` - Make cuDF/cuGraph use cuda.core stream
### From cuGraph:
- `cugraph.Graph(directed=True)` - Create directed graph structure
- `Graph.from_cudf_edgelist()` - Build graph from edge list DataFrame
- `cugraph.pagerank()` - GPU-accelerated PageRank algorithm
### From cuDF:
- `cudf.DataFrame()` - GPU DataFrame for edge lists
## Requirements
### Hardware:
- NVIDIA GPU with Compute Capability 7.0 or higher
- Minimum GPU memory: 512 MB (for 10K node graph)
### Software:
- CUDA Toolkit 13.0 or newer
- Python 3.10 or newer
- See requirements.txt for package dependencies
## Installation
```bash
cd /path/to/cuda-samples/python/2_CoreConcepts/pageRank
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
## How to Run
```bash
python pageRank.py
```
## Algorithm
The PageRank formula iteratively computes node importance:
```
PR(v) = (1-d)/N + d * Σ PR(u)/out_degree(u)
```
Where:
- `d` = damping factor (typically 0.85)
- `N` = total number of nodes
- Sum is over all nodes `u` that link to `v`
## Expected Output
```
============================================================
PageRank Algorithm (using RAPIDS cuGraph)
============================================================
Device: NVIDIA GeForce RTX ...
Compute Capability: sm_XX
Graph Parameters:
Nodes: 10,000
Avg edges/node: 15
Total edges: ~150,000
Avg in-degree: 14.9
------------------------------------------------------------
GPU PageRank (RAPIDS cuGraph)
------------------------------------------------------------
Time: X.XXX ms
Top 5 nodes by PageRank:
1. Node XXXXX: 0.XXXXXX
...
------------------------------------------------------------
CPU PageRank (Reference)
------------------------------------------------------------
Time: XXXX.XXX ms
Iterations: XX
------------------------------------------------------------
PERFORMANCE SUMMARY
------------------------------------------------------------
GPU (cuGraph): X.XXX ms
CPU (Reference): XXXX.XXX ms
Speedup: XXXX.Xx
------------------------------------------------------------
VERIFICATION
------------------------------------------------------------
GPU vs CPU PageRank scores: Test PASSED
PageRank Properties:
Sum of scores: 1.000000 (should be ~1.0)
Sum check: ✓
Done
```
## Files
- `pageRank.py` - Python implementation using RAPIDS cuGraph
- `README.md` - This file
- `requirements.txt` - Sample dependencies
## Why cuGraph?
RAPIDS cuGraph provides production-grade, GPU-accelerated graph analytics:
- **Highly optimized** - Uses advanced GPU parallelization techniques
- **Scalable** - Handles graphs with billions of edges
- **Easy to use** - Simple Python API similar to NetworkX
- **Integrated** - Works seamlessly with cuDF, cuML, and other RAPIDS libraries
## Applications
- Web page ranking (original Google PageRank)
- Social network influence analysis
- Citation network analysis
- Recommendation systems
- Fraud detection in financial networks
## See Also
- [RAPIDS cuGraph Documentation](https://docs.rapids.ai/api/cugraph/stable/)
- [cuGraph GitHub Repository](https://github.com/rapidsai/cugraph)
- [RAPIDS Installation Guide](https://rapids.ai/start.html)

View File

@@ -0,0 +1,346 @@
# 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.
"""
PageRank Algorithm
Demonstrates GPU-accelerated PageRank computation for graph analysis:
- Graph representation using edge lists and cuDF DataFrames
- GPU-optimized PageRank via RAPIDS cuGraph library
- Performance comparison: cuGraph GPU vs CPU reference
Uses RAPIDS cuGraph for production-grade graph analytics on GPU.
PageRank Algorithm:
PR(v) = (1-d)/N + d * sum(PR(u)/out_degree(u)) for all u linking to v
where d = damping factor (typically 0.85), N = number of nodes
"""
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
from cuda_samples_utils import print_gpu_info, verify_array_result # noqa: E402
try:
import cudf
import cugraph
import cupy as cp
import numpy as np
from cuda.core import Device, EventOptions, Stream
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Install with: pip install -r requirements.txt")
sys.exit(1)
def generate_random_graph(
num_nodes: int,
avg_edges_per_node: int = 10,
seed: int = 42,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
"""
Generate a random directed graph as edge list.
Parameters
----------
num_nodes : int
Number of nodes in the graph
avg_edges_per_node : int
Average number of outgoing edges per node
seed : int
Random seed for reproducibility
Returns
-------
tuple[np.ndarray, np.ndarray, np.ndarray]
(sources, destinations, out_degree) arrays
"""
rng = np.random.default_rng(seed)
sources_list: list[int] = []
destinations_list: list[int] = []
out_degree = np.zeros(num_nodes, dtype=np.int32)
for src in range(num_nodes):
# Random number of outgoing edges (Poisson distribution)
n_edges = max(1, rng.poisson(avg_edges_per_node))
n_edges = min(n_edges, num_nodes - 1)
# Random destinations (no self-loops); rejection sampling avoids O(N²) memory
dests: set[int] = set()
while len(dests) < n_edges:
d = int(rng.integers(0, num_nodes))
if d != src:
dests.add(d)
dests = np.array(list(dests), dtype=np.int32)
for dst in dests:
sources_list.append(src)
destinations_list.append(dst)
out_degree[src] = len(dests)
sources = np.array(sources_list, dtype=np.int32)
destinations = np.array(destinations_list, dtype=np.int32)
return sources, destinations, out_degree
def pagerank_cpu(
sources: np.ndarray,
destinations: np.ndarray,
out_degree: np.ndarray,
num_nodes: int,
damping: float = 0.85,
max_iterations: int = 100,
tolerance: float = 1e-6,
) -> tuple[np.ndarray, int]:
"""
Compute PageRank on CPU using iterative method.
Parameters
----------
sources : np.ndarray
Source nodes of edges
destinations : np.ndarray
Destination nodes of edges
out_degree : np.ndarray
Outgoing degree for each node
num_nodes : int
Number of nodes
damping : float
Damping factor (default: 0.85)
max_iterations : int
Maximum iterations
tolerance : float
Convergence tolerance
Returns
-------
tuple[np.ndarray, int]
(PageRank scores, iterations until convergence)
"""
# Build incoming edges list for each node
incoming: list[list[int]] = [[] for _ in range(num_nodes)]
for src, dst in zip(sources, destinations):
incoming[dst].append(src)
# Initialize PageRank uniformly
pr = np.ones(num_nodes, dtype=np.float32) / num_nodes
pr_new = np.zeros(num_nodes, dtype=np.float32)
base_score = (1.0 - damping) / num_nodes
for iteration in range(max_iterations):
# Handle dangling nodes (nodes with no outgoing edges)
dangling_sum = np.sum(pr[out_degree == 0])
dangling_contrib = damping * dangling_sum / num_nodes
for v in range(num_nodes):
# Sum contributions from incoming neighbors
incoming_sum = 0.0
for u in incoming[v]:
if out_degree[u] > 0:
incoming_sum += pr[u] / out_degree[u]
pr_new[v] = base_score + damping * incoming_sum + dangling_contrib
# Check convergence
diff = np.sum(np.abs(pr_new - pr))
pr, pr_new = pr_new, pr
if diff < tolerance:
return pr, iteration + 1
return pr, max_iterations
def run_pagerank_benchmark(
num_nodes: int = 10000,
avg_edges: int = 15,
max_iterations: int = 100,
) -> bool:
"""
Run PageRank benchmark comparing cuGraph GPU and CPU performance.
Parameters
----------
num_nodes : int
Number of nodes in the graph
avg_edges : int
Average edges per node
max_iterations : int
Maximum PageRank iterations
Returns
-------
bool
True if benchmark succeeded
"""
print("=" * 60)
print("PageRank Algorithm (using RAPIDS cuGraph)")
print("=" * 60)
# Initialize cuda.core device and stream
device = Device(0)
device.set_current()
stream: Stream = device.create_stream()
print()
print_gpu_info(device)
# Make CuPy/cuDF use our cuda.core stream
cp.cuda.ExternalStream(int(stream.handle)).use()
# Generate random graph
print("\nGraph Parameters:")
print(f" Nodes: {num_nodes:,}")
print(f" Avg edges/node: {avg_edges}")
sources, destinations, out_degree = generate_random_graph(
num_nodes, avg_edges, seed=42
)
total_edges = len(sources)
print(f" Total edges: {total_edges:,}")
print(f" Avg in-degree: {total_edges / num_nodes:.1f}")
# -------------------------------------------------------------------------
# GPU PageRank (cuGraph)
# -------------------------------------------------------------------------
print("\n" + "-" * 60)
print("GPU PageRank (RAPIDS cuGraph)")
print("-" * 60)
# Create cuGraph graph from edge list with store_transposed for optimal perf
gdf = cudf.DataFrame(
{
"src": sources,
"dst": destinations,
}
)
G = cugraph.Graph(directed=True)
G.from_cudf_edgelist(gdf, source="src", destination="dst", store_transposed=True)
event_opts = EventOptions(enable_timing=True)
try:
# Warmup
_ = cugraph.pagerank(G, alpha=0.85, max_iter=100, tol=1e-5)
stream.sync()
# Timed run using cuda.core events
start = stream.record(options=event_opts)
pr_result = cugraph.pagerank(G, alpha=0.85, max_iter=max_iterations, tol=1e-6)
end = stream.record(options=event_opts)
end.sync()
gpu_time_ms = end - start
print(f"Time: {gpu_time_ms:.3f} ms")
# Extract results sorted by vertex ID (to numpy for verification)
pr_df = pr_result.sort_values("vertex").reset_index(drop=True)
pr_gpu = pr_df["pagerank"].to_numpy()
# Show top PageRank nodes
top_k = 5
top_df = pr_result.nlargest(top_k, "pagerank")
print(f"\nTop {top_k} nodes by PageRank:")
for i, row in enumerate(top_df.to_pandas().itertuples()):
print(f" {i + 1}. Node {row.vertex:5d}: {row.pagerank:.6f}")
# -------------------------------------------------------------------------
# CPU PageRank
# -------------------------------------------------------------------------
print("\n" + "-" * 60)
print("CPU PageRank (Reference)")
print("-" * 60)
cpu_start = time.perf_counter()
pr_cpu, cpu_iters = pagerank_cpu(
sources, destinations, out_degree, num_nodes, max_iterations=max_iterations
)
cpu_end = time.perf_counter()
cpu_time_ms = (cpu_end - cpu_start) * 1000
print(f"Time: {cpu_time_ms:.3f} ms")
print(f"Iterations: {cpu_iters}")
# -------------------------------------------------------------------------
# Performance Summary
# -------------------------------------------------------------------------
print("\n" + "-" * 60)
print("PERFORMANCE SUMMARY")
print("-" * 60)
speedup = cpu_time_ms / gpu_time_ms
print(f"GPU (cuGraph): {gpu_time_ms:.3f} ms")
print(f"CPU (Reference): {cpu_time_ms:.3f} ms")
print(f"Speedup: {speedup:.1f}x")
# -------------------------------------------------------------------------
# Verification
# -------------------------------------------------------------------------
print("\n" + "-" * 60)
print("VERIFICATION")
print("-" * 60)
# Compare GPU and CPU results (cuGraph and CPU ref may converge differently)
print("GPU vs CPU PageRank scores: ", end="")
success = verify_array_result(
pr_gpu, pr_cpu, rtol=1e-2, atol=1e-4, verbose=True
)
# Verify PageRank properties
print("\nPageRank Properties:")
pr_sum = float(np.sum(pr_gpu))
print(f" Sum of scores: {pr_sum:.6f} (should be ~1.0)")
pr_min = float(np.min(pr_gpu))
pr_max = float(np.max(pr_gpu))
print(f" Min score: {pr_min:.6f}")
print(f" Max score: {pr_max:.6f}")
# Check that sum is approximately 1
sum_ok = abs(pr_sum - 1.0) < 0.01
print(f" Sum check: {'✓' if sum_ok else '✗'}")
success = success and sum_ok
return success
finally:
cp.cuda.Stream.null.use()
stream.close()
def main() -> None:
"""Entry point."""
success = run_pagerank_benchmark()
if success:
print("\nDone")
else:
print("\nBenchmark completed with errors")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,8 @@
# PageRank Requirements (RAPIDS cuGraph)
cuda-python>=13.0.0
cuda-core>=0.6.0
cugraph-cu13>=25.0.0
cudf-cu13>=25.0.0
cupy-cuda13x>=13.0.0
numpy>=2.3.2

View File

@@ -0,0 +1,117 @@
# Sample: Parallel Histogram with Atomics (Python)
## Description
Compute histograms on the GPU using atomic operations to handle concurrent updates from multiple threads. This sample demonstrates the modern **cuda.core API** for kernel compilation and launch, comparing two approaches:
1. **Global Atomics** - All threads atomically update a single global histogram
2. **Privatized Histograms** - Each block uses shared memory, then merges to global
## What You'll Learn
- Compiling CUDA C kernels with `cuda.core.Program`
- Configuring kernel launches with `cuda.core.LaunchConfig`
- Launching kernels with `cuda.core.launch()`
- Using **atomic operations** (`atomicAdd`) for thread-safe updates
- Optimizing with **shared memory privatization**
- GPU timing with `cuda.core` Events
## Key Concepts
### Atomic Operations
When multiple threads update the same histogram bin, a race condition occurs. Atomic operations ensure thread-safe updates:
```cuda
atomicAdd(&histogram[data[i]], 1); // Thread-safe increment
```
### Global vs Privatized Atomics
| Approach | Pros | Cons |
|----------|------|------|
| Global | Simple | High contention on popular bins |
| Privatized | Significantly faster | Extra shared memory, synchronization |
## Key APIs
### From `cuda.core`:
- `Device` - Device management and context
- `Program` - Compile CUDA C source code
- `ProgramOptions` - Set architecture, optimization flags
- `LaunchConfig` - Configure grid and block dimensions
- `launch()` - Launch compiled kernel
- `Stream` - Async stream management
- `EventOptions` - Configure events for GPU timing
- `stream.record()` - Record events for timing
### From `cupy`:
- `cp.random.randint()` - Generate random data directly on GPU
- `cp.zeros()` - Allocate zeroed GPU arrays
### CUDA Atomic Functions (in kernel):
- `atomicAdd()` - Thread-safe addition
## Requirements
### Hardware:
- NVIDIA GPU with CUDA support
### Software:
- CUDA Toolkit 13.0 or newer
- Python 3.10 or newer
- See `requirements.txt` for Python packages
## Installation
```bash
pip install -r requirements.txt
```
## How to Run
```bash
python parallelHistogram.py
```
## Expected Output
```
============================================================
Parallel Histogram with Atomics (cuda.core)
============================================================
Device: <Your GPU>
Compute Capability: ComputeCapability(major=X, minor=Y)
Compiling CUDA kernels with cuda.core.Program...
Compiled for architecture: sm_XY
Generating 10,000,000 random values on GPU...
Verifying correctness...
Global atomics: PASSED
Privatized atomics: PASSED
Benchmarking (100 iterations)...
Global atomics: X.XXX ms
Privatized atomics: X.XXX ms
Speedup: XXx
Test PASSED
```
## Files
- `parallelHistogram.py` - Main sample using cuda.core
- `README.md` - This file
- `requirements.txt` - Dependencies
## See Also
- [cuda.core Documentation](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- [CUDA Atomic Functions](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#atomic-functions)
- [CUDA Shared Memory](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#shared-memory)

View File

@@ -0,0 +1,237 @@
# 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.
"""
Parallel Histogram with Atomics using cuda.core
This sample demonstrates GPU histogram computation using atomic operations,
showcasing the modern cuda.core API for:
- Kernel compilation (Program, ProgramOptions)
- Kernel launch configuration (LaunchConfig)
- Stream management (Stream)
- Event timing (EventOptions)
Two histogram approaches are compared:
1. Global Atomics - All threads atomically update global memory
2. Privatized Histograms - Shared memory reduces global atomic contention
"""
import sys
try:
import cupy as cp
import numpy as np
from cuda.core import (
Device,
EventOptions,
LaunchConfig,
Program,
ProgramOptions,
launch,
)
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install: pip install -r requirements.txt")
sys.exit(1)
NUM_BINS = 256
# CUDA C source code for both histogram kernels
HISTOGRAM_KERNELS = r"""
// Global Atomics - simple but high contention on popular bins
extern "C" __global__
void histogram_global(const unsigned char* data, unsigned int* histogram, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int stride = blockDim.x * gridDim.x;
for (int i = idx; i < n; i += stride) {
atomicAdd(&histogram[data[i]], 1);
}
}
// Privatized - uses shared memory to reduce global atomic contention
extern "C" __global__
void histogram_privatized(const unsigned char* data, unsigned int* histogram, int n) {
__shared__ unsigned int local_hist[256];
int tid = threadIdx.x;
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int stride = blockDim.x * gridDim.x;
// Initialize shared memory
for (int i = tid; i < 256; i += blockDim.x)
local_hist[i] = 0;
__syncthreads();
// Accumulate into shared memory (fast)
for (int i = idx; i < n; i += stride)
atomicAdd(&local_hist[data[i]], 1);
__syncthreads();
// Merge to global (fewer atomics)
for (int i = tid; i < 256; i += blockDim.x)
if (local_hist[i] > 0)
atomicAdd(&histogram[i], local_hist[i]);
}
"""
def main():
print("=" * 60)
print("Parallel Histogram with Atomics (cuda.core)")
print("=" * 60)
# Initialize device using cuda.core
device = Device(0)
device.set_current()
print(f"\nDevice: {device.name}")
print(f"Compute Capability: {device.compute_capability}")
# Create stream using cuda.core
stream = device.create_stream()
# Make CuPy use the same stream for correct ordering (avoids null-stream sync)
cp.cuda.Stream.from_external(stream).use()
try:
_run_histogram(device, stream)
finally:
cp.cuda.Stream.null.use() # Restore CuPy to default stream before closing
stream.close()
def _run_histogram(device, stream):
"""Run histogram computation and benchmarking."""
# Compile CUDA kernels using cuda.core.Program
print("\nCompiling CUDA kernels with cuda.core.Program...")
arch = f"sm_{device.arch}"
options = ProgramOptions(arch=arch)
program = Program(HISTOGRAM_KERNELS, code_type="c++", options=options)
object_code = program.compile("cubin")
kernel_global = object_code.get_kernel("histogram_global")
kernel_privatized = object_code.get_kernel("histogram_privatized")
print(f" Compiled for architecture: {arch}")
# Generate test data directly on GPU (more efficient than CPU->GPU copy)
n = 10_000_000
print(f"\nGenerating {n:,} random values on GPU...")
data_gpu = cp.random.randint(0, 256, size=n, dtype=cp.uint8)
hist_gpu = cp.zeros(NUM_BINS, dtype=cp.uint32)
# Compute reference histogram on CPU for verification
data_cpu = cp.asnumpy(data_gpu)
hist_cpu, _ = np.histogram(data_cpu, bins=NUM_BINS, range=(0, 256))
hist_cpu = hist_cpu.astype(np.uint32)
# Configure kernel launch using cuda.core.LaunchConfig
block_size = 256
grid_size = min((n + block_size - 1) // block_size, 1024)
config = LaunchConfig(grid=(grid_size,), block=(block_size,))
print("\nVerifying correctness...")
# Ensure CuPy allocations complete before kernel launch on our stream
stream.sync()
# Launch global atomics kernel (hist_gpu is already zeros from cp.zeros)
launch(
stream, config, kernel_global, data_gpu.data.ptr, hist_gpu.data.ptr, np.int32(n)
)
stream.sync()
hist_global = cp.asnumpy(hist_gpu)
global_ok = np.array_equal(hist_cpu, hist_global)
print(f" Global atomics: {'PASSED' if global_ok else 'FAILED'}")
# Reset histogram and launch privatized kernel (fill on same stream)
hist_gpu.fill(0)
launch(
stream,
config,
kernel_privatized,
data_gpu.data.ptr,
hist_gpu.data.ptr,
np.int32(n),
)
stream.sync()
hist_privatized = cp.asnumpy(hist_gpu)
privatized_ok = np.array_equal(hist_cpu, hist_privatized)
print(f" Privatized atomics: {'PASSED' if privatized_ok else 'FAILED'}")
if not (global_ok and privatized_ok):
sys.exit(1)
# Benchmark using cuda.core Events (explicit Event objects recorded on stream)
print("\nBenchmarking (100 iterations)...")
num_iterations = 100
event_opts = EventOptions(enable_timing=True)
start_event = device.create_event(options=event_opts)
end_event = device.create_event(options=event_opts)
# Benchmark global atomics
stream.record(start_event)
for _ in range(num_iterations):
hist_gpu.fill(0)
launch(
stream,
config,
kernel_global,
data_gpu.data.ptr,
hist_gpu.data.ptr,
np.int32(n),
)
stream.record(end_event)
end_event.sync()
time_global = (end_event - start_event) / num_iterations
# Benchmark privatized
stream.record(start_event)
for _ in range(num_iterations):
hist_gpu.fill(0)
launch(
stream,
config,
kernel_privatized,
data_gpu.data.ptr,
hist_gpu.data.ptr,
np.int32(n),
)
stream.record(end_event)
end_event.sync()
time_privatized = (end_event - start_event) / num_iterations
print(f" Global atomics: {time_global:.3f} ms")
print(f" Privatized atomics: {time_privatized:.3f} ms")
print(f" Speedup: {time_global / time_privatized:.1f}x")
print("\nTest PASSED")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,7 @@
# Parallel Histogram with Atomics Sample Requirements
# Requires Python 3.10+, CUDA Toolkit 13.0+
cuda-python>=13.0.0
cuda-core>=0.6.0
numpy>=2.3.2
cupy-cuda13x>=13.0.0

View File

@@ -0,0 +1,119 @@
# Sample: Parallel Reduction (Python)
## Description
Efficiently sum a large array on GPU using parallel reduction. This sample demonstrates:
1. **Custom CUDA kernel** showing reduction tree pattern and synchronization
2. **cuda.compute.reduce_into()** for production-ready reduction
## What You'll Learn
- **Reduction tree pattern**: Divide-and-conquer parallel algorithm
- **Thread synchronization**: Using `__syncthreads()` for coordination
- **Avoiding warp divergence**: Sequential thread IDs vs strided IDs
## Key Concepts
### Reduction Tree Pattern
Parallel reduction uses a tree-based approach where each iteration halves active elements:
```
Initial: [a0, a1, a2, a3, a4, a5, a6, a7]
Step 1: [a0+a4, a1+a5, a2+a6, a3+a7] threads 0-3 active
Step 2: [a0+a2+a4+a6, a1+a3+a5+a7] threads 0-1 active
Step 3: [sum of all] thread 0 only
```
This requires only `log2(N)` steps to reduce N elements.
### Avoiding Warp Divergence
```c
// Good: Sequential thread IDs (warps stay coherent)
if (tid < s) {
sdata[tid] += sdata[tid + s];
}
// Bad: Strided IDs (causes warp divergence)
if (tid % (2 * s) == 0) { // Don't do this!
sdata[tid] += sdata[tid + s];
}
```
## Requirements
### Hardware
- NVIDIA GPU with CUDA support
### Software
- CUDA Toolkit 13.0+
- Python 3.10+
- `cuda-python` (13.0.0+)
- `cuda-core` (>=0.6.0)
- `cuda-cccl` (1.0.0+)
- `cupy` (13.0.0+)
- `numpy` (>=2.3.2)
## Installation
```bash
pip install -r requirements.txt
```
## How to Run
```bash
python parallelReduction.py
```
## Expected Output
```
======================================================================
Parallel Reduction - Efficient GPU Array Summation
======================================================================
Device: <your GPU name>
Compute Capability: <version>
Array size: 1,048,576 elements (4.2 MB)
Expected sum: <value>
Compiling custom CUDA kernel...
======================================================================
PART 1: Custom Kernel (Educational)
======================================================================
Reduction tree kernel: <result>
Expected: <result>
Error: <small value>
Time: <varies> ms
======================================================================
PART 2: cuda.compute.reduce_into() (Production)
======================================================================
cuda.compute result: <result>
Expected: <result>
Error: <small value>
Time: <varies> ms
Test PASSED!
```
Note: Exact values vary due to random input data. `cuda.compute.reduce_into()` is typically faster than the custom kernel because it calls CUB's `DeviceReduce`, which uses highly tuned, architecture‑specific kernels and optimized memory access patterns.
## Files
- `parallelReduction.py` - Custom kernel + cuda.compute comparison
- `README.md` - This documentation
- `requirements.txt` - Python dependencies
## See Also
- [Mark Harris - Optimizing Parallel Reduction in CUDA](https://developer.download.nvidia.com/assets/cuda/files/reduction.pdf)
- [cuda.core Documentation](https://nvidia.github.io/cuda-python/cuda-core/latest/)

View File

@@ -0,0 +1,375 @@
# 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.
"""
Parallel Reduction using cuda.core and cuda.compute
Demonstrates efficient parallel summation of large arrays on GPU:
1. Custom CUDA kernel showing reduction tree pattern and synchronization
2. cuda.compute.reduce_into() for production-ready reduction
Key Concepts:
- Reduction tree pattern: Divide-and-conquer parallel algorithm
- Thread synchronization: Using __syncthreads() for coordination
- Sequential thread IDs: How to avoid warp divergence
- cuda.core Stream integration with CuPy via ExternalStream
"""
import math
import sys
from pathlib import Path
# Add Utilities to path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
try:
import cupy as cp
import numpy as np
from cuda.compute import OpKind, reduce_into
from cuda.core import (
Device,
Kernel,
LaunchConfig,
Program,
ProgramOptions,
Stream,
launch,
)
from cuda_samples_utils import print_gpu_info, verify_array_result
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
# =============================================================================
# CUDA Kernel: Parallel Reduction (optimized - no warp divergence)
# =============================================================================
REDUCTION_KERNEL: str = r"""
extern "C" __global__
void reduce_sum(const float* __restrict__ input,
float* __restrict__ output, int n) {
/*
* Parallel reduction using grid-stride loop (canonical pattern) and
* sequential thread IDs for the reduction tree (avoids warp divergence).
*
* Grid-stride loop: each thread processes multiple elements
* for (i = tid; i < n; i += gridDim.x * blockDim.x)
*
* Reduction tree: sequential addressing keeps warps coherent.
*/
extern __shared__ float sdata[];
unsigned int tid = threadIdx.x;
unsigned int grid_stride = (unsigned int)gridDim.x * blockDim.x;
float sum = 0.0f;
for (unsigned int i = blockIdx.x * blockDim.x + threadIdx.x; i < n;
i += grid_stride) {
sum += input[i];
}
sdata[tid] = sum;
__syncthreads();
// Reduction in shared memory (sequential addressing - no divergence)
for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) {
if (tid < s) {
sdata[tid] += sdata[tid + s];
}
__syncthreads(); // Wait for all threads before next iteration
}
// Thread 0 writes block result
if (tid == 0) {
output[blockIdx.x] = sdata[0];
}
}
"""
def compile_kernel(device: Device) -> Kernel:
"""Compile the reduction kernel for the given device."""
arch = f"sm_{device.arch}"
options = ProgramOptions(arch=arch)
program = Program(REDUCTION_KERNEL, code_type="c++", options=options)
return program.compile(target_type="cubin").get_kernel("reduce_sum")
def reduction_stage_output_counts(n: int, block_size: int) -> list[int]:
"""Lengths of intermediate arrays for each multi-launch reduction stage."""
counts: list[int] = []
while n > 1:
num_blocks = math.ceil(n / block_size)
counts.append(num_blocks)
n = num_blocks
return counts
def reduce_custom(
stream: Stream,
kernel: Kernel,
d_input: cp.ndarray,
block_size: int = 256,
sync: bool = True,
work_buffers: list[cp.ndarray] | None = None,
) -> float | cp.ndarray:
"""
Perform parallel reduction using custom CUDA kernel.
Uses multiple kernel launches to reduce array to single value.
Each launch reduces by factor of block_size.
When sync=True (default), syncs and returns the scalar result.
When sync=False, returns the 1-element array without syncing;
caller must sync before reading (avoids host overhead in benchmarks).
work_buffers: optional list of device arrays, one per stage, with length
at least each stage's output count (from ``reduction_stage_output_counts``).
When provided, avoids per-call allocation (e.g. for benchmarking).
"""
n = len(d_input)
current = d_input
stage = 0
if work_buffers is not None:
expected_counts = reduction_stage_output_counts(n, block_size)
if len(work_buffers) != len(expected_counts):
msg = (
f"work_buffers length {len(work_buffers)} != "
f"{len(expected_counts)} stages"
)
raise ValueError(msg)
while n > 1:
num_blocks = math.ceil(n / block_size)
if work_buffers is not None:
d_output = work_buffers[stage]
if d_output.size < num_blocks:
msg = f"work_buffers[{stage}] size {d_output.size} < {num_blocks}"
raise ValueError(msg)
if d_output.size != num_blocks:
d_output = d_output[:num_blocks]
else:
d_output = cp.empty(num_blocks, dtype=cp.float32)
config = LaunchConfig(
grid=(num_blocks, 1, 1),
block=(block_size, 1, 1),
shmem_size=block_size * 4, # float = 4 bytes
)
launch(
stream,
config,
kernel,
current.data.ptr,
d_output.data.ptr,
np.int32(n),
)
current = d_output
n = num_blocks
stage += 1
if sync:
stream.sync()
return float(current[0])
return current
def benchmark_custom(
stream: Stream,
kernel: Kernel,
d_input: cp.ndarray,
num_runs: int = 10,
block_size: int = 256,
) -> tuple[float, float]:
"""Benchmark custom reduction kernel using cuda.core events."""
stage_counts = reduction_stage_output_counts(len(d_input), block_size)
work_buffers = [cp.empty(c, dtype=cp.float32) for c in stage_counts]
# Warmup run (with sync to get valid result)
_ = reduce_custom(
stream, kernel, d_input, block_size=block_size, work_buffers=work_buffers
)
event_opts = {"enable_timing": True}
start_event = stream.device.create_event(options=event_opts)
end_event = stream.device.create_event(options=event_opts)
times: list[float] = []
result = 0.0
for _ in range(num_runs):
stream.record(start_event)
d_result = reduce_custom(
stream,
kernel,
d_input,
block_size=block_size,
sync=False,
work_buffers=work_buffers,
)
stream.record(end_event)
end_event.sync()
result = float(d_result[0])
times.append(end_event - start_event)
return result, float(np.mean(times))
def benchmark_cuda_compute(
stream: Stream,
d_input: cp.ndarray,
num_runs: int = 10,
) -> tuple[float, float]:
"""Benchmark cuda.compute.reduce_into() using cuda.core events."""
h_init = np.array([0.0], dtype=np.float32)
# Warmup (includes JIT compilation)
d_warmup = cp.empty(1, dtype=cp.float32)
reduce_into(
d_in=d_input,
d_out=d_warmup,
op=OpKind.PLUS,
num_items=len(d_input),
h_init=h_init,
stream=stream,
)
stream.sync()
d_output = cp.empty(1, dtype=cp.float32)
event_opts = {"enable_timing": True}
start_event = stream.device.create_event(options=event_opts)
end_event = stream.device.create_event(options=event_opts)
times: list[float] = []
result = 0.0
for _ in range(num_runs):
stream.record(start_event)
reduce_into(
d_in=d_input,
d_out=d_output,
op=OpKind.PLUS,
num_items=len(d_input),
h_init=h_init,
stream=stream,
)
stream.record(end_event)
end_event.sync()
result = float(d_output[0])
times.append(end_event - start_event)
return result, float(np.mean(times))
def main() -> bool:
"""Main function demonstrating parallel reduction."""
print("=" * 70)
print("Parallel Reduction - Efficient GPU Array Summation")
print("=" * 70)
device = Device(0)
device.set_current()
stream = device.create_stream()
cp_stream = cp.cuda.ExternalStream(int(stream.handle))
print()
print_gpu_info(device)
array_size = 1 << 20 # 1M elements
h_input = np.random.rand(array_size).astype(np.float32)
expected_sum = float(np.sum(h_input))
print(f"\nArray size: {array_size:,} elements ({array_size * 4 / 1e6:.1f} MB)")
print(f"Expected sum: {expected_sum:.6f}")
print("\nCompiling custom CUDA kernel...")
kernel = compile_kernel(device)
try:
with cp_stream:
d_input = cp.asarray(h_input)
# ======================================================================
# Part 1: Custom Kernel
# ======================================================================
print("\n" + "=" * 70)
print("PART 1: Custom Kernel (Educational)")
print("=" * 70)
result, time_ms = benchmark_custom(stream, kernel, d_input)
print(f"\nReduction tree kernel: {result:>14.2f}")
print(f"Expected: {expected_sum:>14.2f}")
print(f"Time: {time_ms:>14.3f} ms")
# ======================================================================
# Part 2: cuda.compute (Production)
# ======================================================================
print("\n" + "=" * 70)
print("PART 2: cuda.compute.reduce_into() (Production)")
print("=" * 70)
result_cc, time_cc = benchmark_cuda_compute(stream, d_input)
print(f"\ncuda.compute result: {result_cc:>14.2f}")
print(f"Expected: {expected_sum:>14.2f}")
print(f"Time: {time_cc:>14.3f} ms")
# Verify both results using principled rtol/atol
with cp_stream:
d_expected = cp.array([expected_sum], dtype=cp.float32)
custom_ok = verify_array_result(
cp.array([result], dtype=cp.float32),
d_expected,
rtol=1e-5,
atol=1e-8,
verbose=False,
)
compute_ok = verify_array_result(
cp.array([result_cc], dtype=cp.float32),
d_expected,
rtol=1e-5,
atol=1e-8,
verbose=False,
)
if custom_ok and compute_ok:
print("\nTest PASSED!")
return True
else:
print("\nTest FAILED - Error too large!")
return False
finally:
stream.close()
if __name__ == "__main__":
sys.exit(0 if main() else 1)

View File

@@ -0,0 +1,7 @@
# Parallel Reduction Sample Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0
cuda-cccl>=1.0.0
cupy-cuda13x>=13.0.0
numpy>=2.3.2

View File

@@ -0,0 +1,83 @@
# Prefix Sum (Scan)
Demonstrates parallel prefix sum (scan) algorithms using cuda.compute with cuda.core stream management.
## Overview
- Inclusive scan: `output[i] = [init_value] + input[0] + input[1] + ... + input[i]`
- Exclusive scan: `output[i] = init_value + input[0] + input[1] + ... + input[i-1]`
- Uses cuda.compute APIs for optimized CUB-based implementations
- Uses cuda.core APIs for device and stream management
- Demonstrates CuPy integration via `ExternalStream`
## Requirements
### Hardware
- NVIDIA GPU with CUDA support
### Software
- CUDA Toolkit 13.0+
- Python 3.10+
- `cuda-python` (13.0.0+)
- `cuda-core` (>=0.6.0)
- `cuda-cccl` (1.0.0+)
- `cupy-cuda13x` (13.0.0+)
- `numpy` (>=2.3.2)
## Usage
```bash
# Create and activate virtual environment
python -m venv venv
source venv/bin/activate # Linux/macOS
# venv\Scripts\activate # Windows
# Install dependencies
pip install -r requirements.txt
# Run sample
python prefixSum.py
```
## Key Concepts
| Scan Type | Formula | First Element |
|-----------|---------|---------------|
| Inclusive | `output[i] = [init_value] + Σ input[0..i]` | `[init_value] + input[0]` |
| Exclusive | `output[i] = init_value + Σ input[0..i-1]` | `init_value` (typically `0`, the identity for sum) |
### Stream Management
This sample demonstrates proper stream usage across libraries:
```python
# Create stream with cuda.core
stream = device.create_stream()
# Wrap for CuPy compatibility (requires int handle)
cp_stream = cp.cuda.ExternalStream(int(stream.handle))
# Use with CuPy operations
with cp_stream:
d_input = cp.asarray(data)
d_output = cp.empty_like(d_input)
# Pass to cuda.compute
inclusive_scan(
d_in=d_input,
d_out=d_output,
op=OpKind.PLUS,
init_value=None,
num_items=len(d_input),
stream=stream,
)
```
## Applications
- Stream compaction
- Radix sort
- Histogram computation
- Polynomial evaluation

View File

@@ -0,0 +1,199 @@
# 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.
"""
Prefix Sum (Scan)
Demonstrates parallel prefix sum algorithms using cuda.compute:
- Inclusive scan: output[i] = [init_value] + input[0] + ... + input[i]
- Exclusive scan: output[i] = init_value + input[0] + ... + input[i-1]
Uses cuda.compute APIs for optimized CUB-based scan operations.
Uses cuda.core APIs for device and stream management.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
try:
import cupy as cp
import numpy as np
from cuda.compute import OpKind, exclusive_scan, inclusive_scan
from cuda.core import Device, EventOptions
from cuda_samples_utils import print_gpu_info, verify_array_result
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
def main() -> bool:
"""Run prefix sum sample. Returns True if all tests passed."""
print("=" * 60)
print("Prefix Sum (Scan) - Using cuda.compute")
print("=" * 60)
device = Device(0)
device.set_current()
stream = device.create_stream()
cp_stream = cp.cuda.ExternalStream(int(stream.handle))
ok = True
try:
print()
print_gpu_info(device)
h_input = np.array([3, 1, 4, 1, 5, 9, 2, 6], dtype=np.int32)
init_value = np.array([0], dtype=np.int32)
# =========================================================================
# Inclusive Scan
# =========================================================================
print("\n" + "-" * 60)
print("INCLUSIVE SCAN")
print("-" * 60)
print(
"Formula: output[i] = [init_value] + input[0] + input[1] + ... + input[i]"
)
with cp_stream:
d_input = cp.asarray(h_input)
d_output = cp.empty_like(d_input)
print(f"\nInput: {h_input.tolist()}")
inclusive_scan(
d_in=d_input,
d_out=d_output,
op=OpKind.PLUS,
init_value=None,
num_items=len(h_input),
stream=stream,
)
stream.sync()
print(f"Output: {cp.asnumpy(d_output).tolist()}")
with cp_stream:
expected = cp.asarray(np.cumsum(h_input))
ok &= verify_array_result(d_output, expected, rtol=0, atol=0)
# =========================================================================
# Exclusive Scan
# =========================================================================
print("\n" + "-" * 60)
print("EXCLUSIVE SCAN")
print("-" * 60)
print("Formula: output[i] = init_value + input[0] + ... + input[i-1]")
with cp_stream:
d_output = cp.empty_like(d_input)
print(f"\nInput: {h_input.tolist()}")
exclusive_scan(
d_in=d_input,
d_out=d_output,
op=OpKind.PLUS,
init_value=init_value,
num_items=len(h_input),
stream=stream,
)
stream.sync()
print(f"Output: {cp.asnumpy(d_output).tolist()}")
with cp_stream:
expected = cp.asarray(np.concatenate([init_value, np.cumsum(h_input)[:-1]]))
ok &= verify_array_result(d_output, expected, rtol=0, atol=0)
# =========================================================================
# Large Array Performance
# =========================================================================
print("\n" + "-" * 60)
print("PERFORMANCE (10M elements)")
print("-" * 60)
N = 10_000_000
with cp_stream:
d_large_in = cp.ones(N, dtype=np.int32)
d_large_out = cp.empty_like(d_large_in)
inclusive_scan(
d_in=d_large_in,
d_out=d_large_out,
op=OpKind.PLUS,
init_value=None,
num_items=N,
stream=stream,
)
stream.sync()
event_opts = EventOptions(enable_timing=True)
start_event = device.create_event(options=event_opts)
end_event = device.create_event(options=event_opts)
num_iterations = 10
stream.record(start_event)
for _ in range(num_iterations):
inclusive_scan(
d_in=d_large_in,
d_out=d_large_out,
op=OpKind.PLUS,
init_value=None,
num_items=N,
stream=stream,
)
stream.record(end_event)
end_event.sync()
elapsed_ms = (end_event - start_event) / num_iterations
print(f"Inclusive scan: {elapsed_ms:.3f} ms")
print(f"Throughput: {N / elapsed_ms / 1e6:.1f} M elements/ms")
# =========================================================================
# Summary
# =========================================================================
print("\n" + "=" * 60)
print("KEY CONCEPTS")
print("=" * 60)
print("• Inclusive: output[i] includes input[i]")
print("• Exclusive: output[i] excludes input[i], starts with init_value")
print("• cuda.compute provides CUB-based optimized implementations")
print("• cuda.core Stream integrates with CuPy via ExternalStream")
print("• Applications: stream compaction, radix sort, histograms")
print("=" * 60)
return ok
finally:
cp.cuda.Stream.null.use()
stream.close()
if __name__ == "__main__":
success = main()
if not success:
sys.exit(1)

View File

@@ -0,0 +1,8 @@
# Prefix Sum Sample Requirements
# Requires Python 3.10+, CUDA Toolkit 13.0+
cuda-python>=13.0.0
cuda-core>=0.6.0
cuda-cccl>=1.0.0
cupy-cuda13x>=13.0.0
numpy>=2.3.2

View File

@@ -0,0 +1,206 @@
# processCheckpoint (Python)
## Description
This sample demonstrates how to use the **CUDA process checkpoint API**
via `cuda.core.checkpoint.Process` to suspend, capture, and restore the
CUDA state of a running Linux process.
CUDA process checkpointing is the driver-level primitive that powers
CRIU + `cuda-checkpoint` integration.
The sample:
1. Allocates a GPU buffer and fills it with a deterministic pattern
via a small kernel.
2. Reads the buffer back to host and computes a SHA-256 hash.
3. Runs the full checkpoint lifecycle on its own process:
`lock → checkpoint → restore → unlock`.
4. Reads the buffer back again and verifies that the hash is
unchanged, proving that GPU memory contents survived the round
trip.
The sample prints the CUDA process state after each step so the
full state machine is visible:
```
lock() checkpoint() restore() unlock()
running ---------> locked ------------> checkpointed -----------> locked ---------> running
```
## What You'll Learn
- Creating a `cuda.core.checkpoint.Process` for the current process
by PID and observing its `.state` transitions.
- Running the full `lock → checkpoint → restore → unlock` cycle with
a lock timeout.
- The fact that `restore()` leaves the process in the `locked` state;
you must still call `unlock()` to return to `running`.
- Verifying that GPU memory is preserved across the checkpoint
round-trip by comparing SHA-256 hashes of the buffer before and
after.
- The rough cost of each step (checkpoint and restore dominate and
scale with the device-memory footprint being captured).
## Key Libraries
- [`cuda.core`](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- device management, memory allocation, kernel compilation and
launch, and the `checkpoint.Process` wrapper.
- [`cuda.bindings`](https://nvidia.github.io/cuda-python/cuda-bindings/latest/)
- used directly for a pageable `cuMemcpyDtoH`.
## Key APIs
### From `cuda.core.checkpoint`
- `checkpoint.Process(pid)` - create a handle to a CUDA process by
PID. Accepts `os.getpid()` for the self-checkpoint case shown
here.
- `Process.state` - one of `"running"`, `"locked"`, `"checkpointed"`,
or `"failed"`.
- `Process.lock(timeout_ms=…)` - block further CUDA API calls on the
process; completes already-submitted work. Always pass a non-zero
timeout to avoid deadlocks.
- `Process.checkpoint()` - copy device memory to host-side driver
allocations and release GPU resources. Process state becomes
`checkpointed`.
- `Process.restore(gpu_mapping=None)` - re-acquire GPU resources and
copy memory back to device. Leaves the process in the `locked`
state.
- `Process.unlock()` - return the process to `running`.
- `Process.restore_thread_id` - thread ID that `restore()` must be
called from in the target process (not used in the self-checkpoint
case here).
### From `cuda.core`
- `Device.set_current()` / `Device.memory_resource.allocate(...)` /
`Stream`, `LaunchConfig`, `Program`, `launch` - standard device,
compile, and launch primitives used to produce the buffer
contents.
### From `cuda.bindings.driver`
- `cuMemcpyDtoH(host_ptr, device_handle, nbytes)` - synchronous D2H
copy into a pageable host buffer.
## Requirements
### Hardware
- Any NVIDIA GPU supported by CUDA process checkpointing. CUDA
checkpointing is currently limited to x86-64 Linux.
### Software
- Linux (the CUDA checkpoint API is Linux-only).
- NVIDIA driver with CUDA process checkpoint support.
- CUDA Toolkit 13.0 or newer.
- Python 3.10 or newer.
- `cuda-core >= 1.0.0`.
## Installation
Install the required packages from `requirements.txt`:
```bash
cd /path/to/cuda-samples/python/2_CoreConcepts/processCheckpoint
pip install -r requirements.txt
```
## How to Run
### Basic usage
```bash
python processCheckpoint.py
```
### Larger GPU footprint to see checkpoint time scale
```bash
python processCheckpoint.py --buffer-mib 512
```
### Use a specific GPU
```bash
python processCheckpoint.py --device 1
```
### All options
```
--device CUDA device ID (default: 0)
--buffer-mib GPU buffer size in MiB (default: 16)
--lock-timeout-ms Timeout passed to Process.lock in ms (default: 5000)
```
## Expected Output
On an RTX 4090 with a 16 MiB buffer:
```
[Process Checkpoint Sample using CUDA Core API]
PID: 748330
Device: NVIDIA GeForce RTX 4090
Compute Capability: sm_89
Buffer size: 16 MiB
Lock timeout: 5000 ms
Compiling kernel ...
Writing deterministic pattern to GPU buffer ...
Buffer hash (before): b045f7975dc23352
Running checkpoint lifecycle on self ...
step duration (ms) state after
--------------------------------------------------
initial - running
lock 0.578 locked
checkpoint 268.369 checkpointed
restore 235.024 locked
unlock 1.648 running
--------------------------------------------------
total 505.618
Buffer hash (before): b045f7975dc23352
Buffer hash (after): b045f7975dc23352
PASS: GPU buffer contents survived checkpoint/restore.
Done
```
**What to look for:**
- The **four state transitions** are all observable: `running →
locked → checkpointed → locked → running`. Note that `restore()`
leaves the process in `locked`, not `running`.
- The **checkpoint and restore steps dominate** the wall-clock time
(hundreds of ms even for a small buffer) - they copy GPU memory to
and from driver-managed host allocations. Increasing
`--buffer-mib` visibly increases the checkpoint time.
- The `lock` and `unlock` steps are essentially free (sub-ms) - they
just flip the process state.
- The SHA-256 **hashes before and after match**, proving the GPU
memory contents survived the round trip.
Exact timings vary with GPU model, driver version, system load, and
the size of the device memory footprint being captured.
## Files
- `processCheckpoint.py` - Python implementation using `cuda.core.checkpoint`
- `README.md` - This file
- `requirements.txt` - Sample dependencies
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [`NVIDIA/cuda-checkpoint`](https://github.com/NVIDIA/cuda-checkpoint)
- the CUDA checkpoint/restore utility, the CRIU plugin, and C
reference programs (`r570-features.c`, `r580-migration-api.c`).
- [Checkpointing CUDA Applications with CRIU](https://developer.nvidia.com/blog/checkpointing-cuda-applications-with-criu/)
- NVIDIA technical blog post on the broader CRIU workflow.

View File

@@ -0,0 +1,248 @@
# 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.
"""
Process Checkpointing Sample using CUDA Core API.
The sample allocates a GPU buffer, fills it with a deterministic
pattern via a kernel, hashes the contents, runs the full
lock/checkpoint/restore/unlock cycle on its own PID, and re-hashes
the buffer afterwards to verify that the GPU memory contents
survived the round trip.
"""
import argparse
import hashlib
import os
import sys
import time
from dataclasses import dataclass
from typing import List
import numpy as np
from cuda.bindings import driver as cudrv
from cuda.core import (
Device,
LaunchConfig,
Program,
ProgramOptions,
checkpoint,
launch,
)
# Small fill kernel: deterministic, non-trivial pattern so the before/after
# hashes would disagree on any bit flip.
KERNEL_SRC = r"""
extern "C" __global__ void fill_pattern(float *out, unsigned long long n)
{
unsigned long long i = (unsigned long long)blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
float v = (float)(i & 0xFFFFu) * 1e-3f + 1.0f;
float u = (float)((i >> 16) & 0xFFFFu) * 1e-4f + 0.5f;
// A handful of dependent ops per element. Deterministic given i.
for (int k = 0; k < 8; ++k) {
v = v * 1.000001f + u;
u = u * 0.999999f + v * 1e-6f;
}
out[i] = v + u;
}
}
"""
@dataclass
class StepTiming:
label: str
duration_ms: float
state_after: str
def _cu_check(result) -> None:
err = result[0]
if int(err) != 0:
raise RuntimeError(f"CUDA driver call failed: {err}")
def compile_fill_kernel(device: Device):
options = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
program = Program(KERNEL_SRC, code_type="c++", options=options)
module = program.compile("cubin", name_expressions=("fill_pattern",))
return module.get_kernel("fill_pattern")
def hash_device_buffer(device_buffer, host: np.ndarray) -> str:
_cu_check(
cudrv.cuMemcpyDtoH(
host.ctypes.data,
device_buffer.handle,
host.nbytes,
)
)
return hashlib.sha256(host.tobytes()).hexdigest()[:16]
def _time_call(fn, *args, **kwargs) -> float:
t0 = time.monotonic()
fn(*args, **kwargs)
return (time.monotonic() - t0) * 1000.0
def run_lifecycle(proc: checkpoint.Process, lock_timeout_ms: int) -> List[StepTiming]:
"""
Drive the full `lock -> checkpoint -> restore -> unlock` cycle on
`proc` and return per-step timings with the state observed after
each step.
Note on state after `restore()`: the driver leaves the process in
the `locked` state. You must still call `unlock()` to return to
`running`.
"""
timings: List[StepTiming] = [StepTiming("initial", 0.0, proc.state)]
ms = _time_call(proc.lock, timeout_ms=lock_timeout_ms)
timings.append(StepTiming("lock", ms, proc.state))
ms = _time_call(proc.checkpoint)
timings.append(StepTiming("checkpoint", ms, proc.state))
ms = _time_call(proc.restore)
timings.append(StepTiming("restore", ms, proc.state))
ms = _time_call(proc.unlock)
timings.append(StepTiming("unlock", ms, proc.state))
return timings
def print_timings(timings: List[StepTiming]) -> None:
print()
header = f"{'step':<14}{'duration (ms)':>18}{'state after':>18}"
print(header)
print("-" * len(header))
total = 0.0
for t in timings:
if t.label == "initial":
dur = "-"
else:
dur = f"{t.duration_ms:.3f}"
total += t.duration_ms
print(f"{t.label:<14}{dur:>18}{t.state_after:>18}")
print("-" * len(header))
print(f"{'total':<14}{total:>18.3f}{'':>18}")
def main():
parser = argparse.ArgumentParser(
description="CUDA process checkpoint sample using cuda.core",
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--device", type=int, default=0, help="CUDA device ID (default: 0)"
)
parser.add_argument(
"--buffer-mib",
type=int,
default=16,
help="GPU buffer size in MiB (default: 16)",
)
parser.add_argument(
"--lock-timeout-ms",
type=int,
default=5000,
help="Timeout passed to Process.lock in ms (default: 5000)",
)
args = parser.parse_args()
if sys.platform != "linux":
print("Error: CUDA process checkpointing is Linux-only.")
return 1
if args.buffer_mib <= 0:
print("Error: --buffer-mib must be positive")
return 1
print("[Process Checkpoint Sample using CUDA Core API]")
print(f"PID: {os.getpid()}")
device = Device(args.device)
device.set_current()
print(f"Device: {device.name}")
print(f"Compute Capability: sm_{device.arch}")
print(f"Buffer size: {args.buffer_mib} MiB")
print(f"Lock timeout: {args.lock_timeout_ms} ms")
print()
print("Compiling kernel ...")
fill_kernel = compile_fill_kernel(device)
buffer_bytes = args.buffer_mib * 1024 * 1024
n_elements = buffer_bytes // 4 # float32
stream = device.create_stream()
device_buffer = device.memory_resource.allocate(buffer_bytes, stream=stream)
try:
print("Writing deterministic pattern to GPU buffer ...")
block = 256
grid = (n_elements + block - 1) // block
cfg = LaunchConfig(grid=grid, block=block)
launch(stream, cfg, fill_kernel, device_buffer, np.uint64(n_elements))
stream.sync()
host = np.empty(n_elements, dtype=np.float32)
hash_before = hash_device_buffer(device_buffer, host)
print(f"Buffer hash (before): {hash_before}")
print()
print("Running checkpoint lifecycle on self ...")
proc = checkpoint.Process(os.getpid())
timings = run_lifecycle(proc, args.lock_timeout_ms)
print_timings(timings)
hash_after = hash_device_buffer(device_buffer, host)
print()
print(f"Buffer hash (before): {hash_before}")
print(f"Buffer hash (after): {hash_after}")
if hash_before != hash_after:
print()
print("FAIL: GPU buffer contents changed across checkpoint/restore.")
return 1
print()
print("PASS: GPU buffer contents survived checkpoint/restore.")
finally:
device_buffer.close(stream)
print()
print("Done")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,3 @@
cuda-python>=13.0.0
cuda-core>=0.7.0
numpy>=2.3.2

View File

@@ -0,0 +1,137 @@
# Sample: Fast Array Sum using Shared Memory (Python)
## Description
Two-stage parallel reduction: each GPU block sums its chunk in **shared memory** (tree reduction, two elements per thread), writes one partial sum per block; the host combines partial sums for the final result.
**Stack:** `cuda-core` for `Device`, stream, events, `Program` / `launch()`. **CuPy** allocates device memory and copies; `launch()` takes device pointers as `ndarray.data.ptr` (Python `int`). Copies run on the same CUDA stream as the kernel via `cp.cuda.Stream.from_external(stream)` (cuda.core `Stream` implements the CUDA stream protocol) and `with cp_stream:`.
## What you will learn
- Shared-memory block reduction and sequential-addressing tree reduction
- `LaunchConfig` with dynamic shared memory and `launch()` with pointer arguments
- Aligning CuPy transfers with a `cuda.core` stream (`Stream.from_external`)
- GPU timing with `EventOptions` / `device.create_event()`
## Key libraries
| Library | Role |
|------------|------|
| `cuda-core`| Device, stream, events, compile, launch |
| `cupy` | `cp.empty`, `cp.asarray`, `cp.asnumpy`, `Stream.from_external` |
| `numpy` | Host data and CPU reference sum |
## Key APIs (quick reference)
- **cuda.core:** `Device`, `create_stream`, `Program` / `ProgramOptions`, `LaunchConfig`, `launch`, `EventOptions`, `create_event`
- **CuPy:** `cp.empty`, `cp.asarray`, `cp.cuda.Stream.from_external(stream)`, `with cp_stream:`, `cp.asnumpy`
## Requirements
- NVIDIA GPU, CUDA-capable driver; **CUDA Toolkit 13+** (for toolchain alignment with `cuda-core`)
- **Python 3.10+**
```bash
pip install -r requirements.txt
```
## How to run
```bash
python reduction.py
```
Defaults: 2²⁴ elements, 256 threads/block, `float`, 100 benchmark iterations.
**Change data type** (selects `blockReduceKernel_int` / `_float` / `_double`):
```bash
python reduction.py --type float # default; 32-bit float
python reduction.py --type double # 64-bit float
python reduction.py --type int # 32-bit integer (exact equality check)
```
Combine with other flags as needed, e.g. `python reduction.py --type int --n 1048576`.
Other main flags: `--n`, `--threads`, `--iterations`. Full list: `python reduction.py --help`.
## Output
Example run (`python reduction.py`, defaults) on **Tesla T10**, compute capability **7.5**:
```
======================================================================
Fast Array Sum using Shared Memory - Two-Stage Reduction
======================================================================
Demonstrates: Efficient parallel reduction using shared memory
Device Information:
Name: Tesla T10
Compute Capability: sm_7.5
Configuration:
Array size: 16,777,216 elements
Data type: float
Memory: 64.00 MB
Threads per block: 256
Two-Stage Reduction Strategy:
Stage 1: GPU block reduction
- Number of blocks: 32768
- Elements per block: 512
- Output: 32768 partial sums
Stage 2: CPU final reduction
- Combine 32768 partial sums → 1 final result
Compiling CUDA kernel...
Kernel 'blockReduceKernel_float' compiled successfully
> Generating random input data...
> Computing reference result on CPU...
CPU time: 2.428208 seconds
> Warming up GPU...
Warm-up completed
> Benchmarking Stage 1 (GPU block reduction)...
Running 100 iterations...
> Running Stage 2 (CPU final reduction)...
======================================================================
Performance Results
======================================================================
Stage 1 (GPU block reduction):
Average time: 0.338404 ms
Throughput: 198.31 GB/s
Stage 2 (CPU final reduction):
Time: 0.078073 ms
(32768 partial sums)
Total time: 0.416477 ms
Speedup vs CPU: 5830.35x
> Validating results...
GPU result: 2147639808.00000000
CPU result: 2147639929.62027407
Test PASSED
======================================================================
Summary
======================================================================
Key optimizations:
- Load 2 elements per thread: 8,388,608 global reads (50% savings)
- Shared memory for reduction: ~10-20x faster than global memory
- Parallel block outputs: 32768 independent writes
Result: 198.31 GB/s throughput
======================================================================
Two-Stage Reduction completed successfully!
======================================================================
```
## Files
`reduction.py` · `requirements.txt` · `README.md`

View File

@@ -0,0 +1,485 @@
# 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
# distribution 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.
"""
Fast Array Sum using Shared Memory - Two-Stage Reduction
Demonstrates efficient parallel reduction using shared memory and
two-stage approach to avoid atomic operation bottlenecks.
Key Features:
- Block-level reduction using shared memory
- Each thread loads 2 elements to reduce global memory traffic
- Sequential addressing tree reduction pattern
- No atomic operations - eliminates serialization bottleneck
- Device memory via CuPy; ``launch()`` takes pointers as ``ndarray.data.ptr``
- CuPy uses ``cp.cuda.Stream.from_external(stream)``.
"""
import argparse
import os
import sys
import time
try:
import cupy as cp
import numpy as np
from cuda.core import (
Device,
EventOptions,
LaunchConfig,
Program,
ProgramOptions,
launch,
)
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install dependencies:")
print(" pip install -r requirements.txt")
sys.exit(1)
# Import utilities
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "Utilities"))
from cuda_samples_utils import verify_array_result # noqa: E402
# Two-stage block reduction kernel
REDUCTION_KERNEL = """
/*
* Block-level reduction kernel using shared memory
*
* Strategy:
* - Each block processes blockSize * 2 elements
* - Uses shared memory for fast intra-block reduction
* - Outputs one partial sum per block (no atomics)
*
* Key optimizations:
* - Load 2 elements per thread (reduces global memory traffic by 50%)
* - Tree reduction with sequential addressing (avoids divergence)
* - Shared memory instead of atomic operations (eliminates bottleneck)
*
* Note: This sample provides separate implementations for each data type
* for clarity. Production code typically uses templates with SharedMemory<T>
* or reinterpret_cast to avoid duplication. See NVIDIA reduction guide for
* template-based approaches.
*/
extern "C" __global__ void blockReduceKernel_int(
const int *__restrict__ input,
int *__restrict__ blockSums,
unsigned int n)
{
extern __shared__ int sdata_int[];
unsigned int tid = threadIdx.x;
unsigned int blockSize = blockDim.x;
unsigned int gid = blockIdx.x * (blockSize * 2) + tid;
// Load 2 elements per thread
int sum = 0;
if (gid < n) sum += input[gid];
if (gid + blockSize < n) sum += input[gid + blockSize];
sdata_int[tid] = sum;
__syncthreads();
// Tree reduction with sequential addressing
for (unsigned int s = blockSize / 2; s > 0; s >>= 1) {
if (tid < s) {
sdata_int[tid] += sdata_int[tid + s];
}
__syncthreads();
}
// Write block result
if (tid == 0) {
blockSums[blockIdx.x] = sdata_int[0];
}
}
extern "C" __global__ void blockReduceKernel_float(
const float *__restrict__ input,
float *__restrict__ blockSums,
unsigned int n)
{
extern __shared__ float sdata_float[];
unsigned int tid = threadIdx.x;
unsigned int blockSize = blockDim.x;
unsigned int gid = blockIdx.x * (blockSize * 2) + tid;
// Load 2 elements per thread
float sum = 0.0f;
if (gid < n) sum += input[gid];
if (gid + blockSize < n) sum += input[gid + blockSize];
sdata_float[tid] = sum;
__syncthreads();
// Tree reduction with sequential addressing
for (unsigned int s = blockSize / 2; s > 0; s >>= 1) {
if (tid < s) {
sdata_float[tid] += sdata_float[tid + s];
}
__syncthreads();
}
// Write block result
if (tid == 0) {
blockSums[blockIdx.x] = sdata_float[0];
}
}
extern "C" __global__ void blockReduceKernel_double(
const double *__restrict__ input,
double *__restrict__ blockSums,
unsigned int n)
{
extern __shared__ double sdata_double[];
unsigned int tid = threadIdx.x;
unsigned int blockSize = blockDim.x;
unsigned int gid = blockIdx.x * (blockSize * 2) + tid;
// Load 2 elements per thread
double sum = 0.0;
if (gid < n) sum += input[gid];
if (gid + blockSize < n) sum += input[gid + blockSize];
sdata_double[tid] = sum;
__syncthreads();
// Tree reduction with sequential addressing
for (unsigned int s = blockSize / 2; s > 0; s >>= 1) {
if (tid < s) {
sdata_double[tid] += sdata_double[tid + s];
}
__syncthreads();
}
// Write block result
if (tid == 0) {
blockSums[blockIdx.x] = sdata_double[0];
}
}
"""
def reduce_cpu(data):
"""Compute sum using Kahan summation for numerical accuracy."""
if len(data) == 0:
return 0
sum_val = float(data[0])
c = 0.0
for i in range(1, len(data)):
y = float(data[i]) - c
t = sum_val + y
c = (t - sum_val) - y
sum_val = t
return sum_val
def _validate_threads_per_block(threads_per_block):
if threads_per_block <= 0 or threads_per_block > 1024:
return "threads per block must be between 1 and 1024"
if (threads_per_block & (threads_per_block - 1)) != 0:
return (
"threads per block must be a power of 2 "
"(required by the shared-memory tree reduction kernel)"
)
return None
def run(
num_elements=1 << 24, threads_per_block=256, test_iterations=100, datatype="float"
):
"""Run two-stage reduction benchmark."""
print("\n" + "=" * 70)
print("Fast Array Sum using Shared Memory - Two-Stage Reduction")
print("=" * 70)
print("\nDemonstrates: Efficient parallel reduction using shared memory")
# Map datatype
dtype_map = {"int": np.int32, "float": np.float32, "double": np.float64}
if datatype not in dtype_map:
print(f"Unknown datatype '{datatype}', using 'float'")
datatype = "float"
dtype = dtype_map[datatype]
itemsize = np.dtype(dtype).itemsize
# Initialize device
device = Device()
device.set_current()
major, minor = device.compute_capability
print("\nDevice Information:")
print(f" Name: {device.name}")
print(f" Compute Capability: sm_{major}.{minor}")
# Configuration
print("\nConfiguration:")
print(f" Array size: {num_elements:,} elements")
print(f" Data type: {datatype}")
print(f" Memory: {num_elements * itemsize / (1024**2):.2f} MB")
print(f" Threads per block: {threads_per_block}")
# Calculate number of blocks
# Each block processes threads_per_block * 2 elements
num_blocks = (num_elements + threads_per_block * 2 - 1) // (threads_per_block * 2)
print("\nTwo-Stage Reduction Strategy:")
print(" Stage 1: GPU block reduction")
print(f" - Number of blocks: {num_blocks}")
print(f" - Elements per block: {threads_per_block * 2}")
print(f" - Output: {num_blocks} partial sums")
print(" Stage 2: CPU final reduction")
print(f" - Combine {num_blocks} partial sums → 1 final result")
# Compile kernel
print("\nCompiling CUDA kernel...")
program_options = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
prog = Program(REDUCTION_KERNEL, code_type="c++", options=program_options)
mod = prog.compile("cubin")
kernel_name = f"blockReduceKernel_{datatype}"
kernel = mod.get_kernel(kernel_name)
print(f" Kernel '{kernel_name}' compiled successfully")
# Generate input data
print("\n> Generating random input data...")
rng = np.random.default_rng(42)
if datatype == "int":
h_input = rng.integers(0, 256, size=num_elements, dtype=dtype)
else:
h_input = (rng.random(num_elements) * 256).astype(dtype)
# cuda.core stream for launch/events; CuPy copies use the same stream via
# Stream.from_external.
stream = device.create_stream()
cp_stream = cp.cuda.Stream.from_external(stream)
try:
d_blockSums = cp.empty(num_blocks, dtype=dtype)
with cp_stream:
d_input = cp.asarray(h_input, dtype=dtype)
stream.sync()
# Compute CPU reference
print("> Computing reference result on CPU...")
cpu_start = time.perf_counter()
cpu_result = reduce_cpu(h_input)
cpu_time = time.perf_counter() - cpu_start
print(f" CPU time: {cpu_time:.6f} seconds")
# Configure launch
shared_mem_bytes = threads_per_block * itemsize
config = LaunchConfig(
grid=num_blocks, block=threads_per_block, shmem_size=shared_mem_bytes
)
# Warm-up
print("\n> Warming up GPU...")
launch(
stream,
config,
kernel,
d_input.data.ptr,
d_blockSums.data.ptr,
np.uint32(num_elements),
)
stream.sync()
print(" Warm-up completed")
# Benchmark Stage 1 (GPU)
print("\n> Benchmarking Stage 1 (GPU block reduction)...")
print(f" Running {test_iterations} iterations...")
# cuda.core event elapsed time (end - start) is in milliseconds (CUDA API).
stage1_times_ms = []
event_options = EventOptions(enable_timing=True)
start_event = stream.device.create_event(options=event_options)
end_event = stream.device.create_event(options=event_options)
for _ in range(test_iterations):
stream.record(start_event)
launch(
stream,
config,
kernel,
d_input.data.ptr,
d_blockSums.data.ptr,
np.uint32(num_elements),
)
stream.record(end_event)
end_event.sync()
stage1_times_ms.append(float(end_event - start_event))
avg_stage1_ms = np.mean(stage1_times_ms)
avg_stage1_s = avg_stage1_ms / 1000.0
# Stage 2 (CPU)
print("\n> Running Stage 2 (CPU final reduction)...")
# Device → Host: after stream sync, partial sums are visible on host.
stream.sync()
with cp_stream:
h_blockSums = cp.asnumpy(d_blockSums)
stage2_start = time.perf_counter()
gpu_result = float(np.sum(h_blockSums))
stage2_time = time.perf_counter() - stage2_start
total_time = avg_stage1_s + stage2_time
# Performance metrics (use seconds for throughput; CPU times are in seconds)
bytes_processed = num_elements * itemsize
throughput = bytes_processed / avg_stage1_s / 1e9
print("\n" + "=" * 70)
print("Performance Results")
print("=" * 70)
print("\nStage 1 (GPU block reduction):")
print(f" Average time: {avg_stage1_ms:.6f} ms")
print(f" Throughput: {throughput:.2f} GB/s")
print("\nStage 2 (CPU final reduction):")
print(f" Time: {stage2_time * 1000:.6f} ms")
print(f" ({num_blocks} partial sums)")
print(f"\nTotal time: {total_time * 1000:.6f} ms")
print(f"Speedup vs CPU: {cpu_time / total_time:.2f}x")
# Validation
print("\n> Validating results...")
if datatype == "int":
print(f" GPU result: {int(gpu_result):,}")
print(f" CPU result: {int(cpu_result):,}")
rtol, atol = 0.0, 0.0
else:
precision = 8 if datatype == "float" else 12
print(f" GPU result: {gpu_result:.{precision}f}")
print(f" CPU result: {cpu_result:.{precision}f}")
if datatype == "float":
rtol, atol = 1e-5, 1e-8 * num_elements
else: # double
rtol, atol = 1e-8, 1e-12 * num_elements
success = verify_array_result(
np.array([gpu_result]),
np.array([cpu_result]),
rtol=rtol,
atol=atol,
verbose=True,
)
# Summary
print("\n" + "=" * 70)
print("Summary")
print("=" * 70)
print("Key optimizations:")
half_reads = num_elements // 2
print(
" - Load 2 elements per thread: "
f"{half_reads:,} global reads (50% savings)"
)
print(" - Shared memory for reduction: ~10-20x faster than global memory")
print(f" - Parallel block outputs: {num_blocks} independent writes")
print(f"Result: {throughput:.2f} GB/s throughput")
print("=" * 70)
if success:
print("Two-Stage Reduction completed successfully!")
else:
print("Two-Stage Reduction FAILED!")
print("=" * 70 + "\n")
return 0 if success else 1
finally:
stream.close()
def main():
"""Main entry point with argument parsing."""
parser = argparse.ArgumentParser(
description="Two-Stage Reduction with Shared Memory",
epilog="See README.md for usage examples and detailed documentation.",
)
parser.add_argument(
"--n",
type=int,
default=1 << 24,
help="Number of elements to reduce (default: 16777216 = 2^24)",
)
parser.add_argument(
"--threads",
type=int,
default=256,
help="Threads per block, power of 2 in [1, 1024] (default: 256)",
)
parser.add_argument(
"--type",
type=str,
default="float",
choices=["int", "float", "double"],
help="Data type for reduction (default: float)",
)
parser.add_argument(
"--iterations",
type=int,
default=100,
help="Number of benchmark iterations (default: 100)",
)
args = parser.parse_args()
# Validate arguments
if args.n <= 0:
print("Error: n must be positive")
return 1
err = _validate_threads_per_block(args.threads)
if err:
print(f"Error: {err}")
return 1
try:
exit_code = run(
num_elements=args.n,
threads_per_block=args.threads,
test_iterations=args.iterations,
datatype=args.type,
)
sys.exit(exit_code)
except Exception as e:
print(f"\nError: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,8 @@
# Python CUDA Reduction Sample Requirements
# Install with: pip install -r requirements.txt
numpy>=2.3.2
cuda-core>=0.6.0
cuda-python>=13.0.0
# Use cupy-cuda13x>=14.0.0 for cp.cuda.Stream.from_external(stream)
cupy-cuda13x>=14.0.0

View File

@@ -0,0 +1,129 @@
# Sample: Single-Pass Multi-Block Reduction with Cooperative Groups (Python)
## Description
Single-kernel, two-stage reduction using **Cooperative Groups** and `grid.sync()` so all blocks synchronize inside one launch—no second kernel or CPU stage for the reduction tree.
**Stack:** `cuda-core` (device, compile, cooperative `launch()`, stream, **CUDA events** for GPU timing). **CuPy** for H↔D copies on the same stream (`Stream.from_external(cuda.core_stream)`, `ndarray.data.ptr` to `launch()`). **`try`/`finally`** closes the stream if cooperative launch fails. Requires **compute capability > 6.0** (Pascal+).
## What you will learn
- `cooperative_groups::grid_group` and `grid.sync()` across the grid
- Cooperative `LaunchConfig(..., cooperative_launch=True)` and sizing blocks for residency
- Timing the GPU path with `EventOptions` / `stream.record()` / event elapsed time
## Key libraries
| Library | Role |
|---------|------|
| `cuda-core` | Device, stream, events, `Program` / `ProgramOptions`, cooperative `launch()` |
| `cupy` | `cp.empty`, `cp.asarray`, `cp.asnumpy`, `Stream.from_external` |
| `numpy` | Host data, reference sum, `default_rng` |
## Requirements
- NVIDIA GPU, **Pascal or newer**; **CUDA Toolkit 13+**; **Python 3.10+**
- NVRTC must see **`cooperative_groups.h`** and **CCCL** headers (`cuda/std/*`)
```bash
pip install -r requirements.txt
```
Pick a CuPy wheel that matches your CUDA major version (e.g. `cupy-cuda13x` in `requirements.txt`).
## How to run
**`--cuda-include-dir` is required** (colon-separated list). Typical desktop layout:
```bash
python reductionMultiBlockCG.py \
--cuda-include-dir /usr/local/cuda/include/cccl:/usr/local/cuda/include
```
**Jetson / split include trees:** pass every directory NVRTC needs in one `--cuda-include-dir` argument, e.g.
`/usr/local/cuda/include/cccl:/usr/local/cuda/targets/sbsa-linux/include` (adjust paths to your image). If headers are scattered, you can instead merge them into one tree with symlinks and point `--cuda-include-dir` at that folder.
Defaults: **2²⁵** elements, threads = device max (capped at 1024), auto `--maxblocks`, **100** iterations. Other flags: `--n`, `--threads`, `--maxblocks`, `--iterations`. See **`python reductionMultiBlockCG.py --help`**.
## Output
```
======================================================================
Single-Pass Multi-Block Reduction with Cooperative Groups
======================================================================
Demonstrates: Multi-stage reduction in a single kernel using grid.sync()
Device Information:
Name: NVIDIA Thor
Compute Capability: sm_11.0
Reduction Configuration:
Number of elements: 33,554,432
Data size: 128.00 MB
Compiling CUDA kernel...
Kernel compiled successfully
Launch Configuration:
Threads per block: 1024
Number of blocks: 20
Total threads: 20,480
Shared memory per block: 4096 bytes
Launch mode: Cooperative (grid-wide sync enabled)
> Generating random input data...
> Computing reference result on CPU...
CPU time: 0.008903 seconds
> Warming up GPU...
Warm-up successful
> Running benchmark (100 iterations)...
> Performance Results:
Average GPU time: 0.977166 ms
Throughput: 137.35 GB/s
Speedup vs CPU: 9.11x
> Validating results...
Test PASSED
======================================================================
Summary
======================================================================
Single-kernel two-stage reduction:
Stage 1: 20 blocks → 20 partial sums
grid.sync() ← All blocks synchronize (KEY innovation)
Stage 2: Block 0 → 1 final result
Total: 1 kernel launch, 137.35 GB/s
Comparison:
• Traditional: 2 kernel launches or kernel + CPU
• This sample: 1 kernel with grid.sync() between stages
• Benefit: Eliminates ~5-20μs launch overhead per stage
======================================================================
Single-Pass Multi-Block Reduction completed successfully!
======================================================================
```
## Troubleshooting (short)
- **Cooperative launch not supported / fails:** need sm_60+; reduce `--maxblocks` or `--threads` so all blocks can be resident.
- **Compile errors missing headers:** extend `--cuda-include-dir` with the path that contains CCCL / cooperative groups (see Jetson note above).
- **Low throughput:** often block count vs occupancy; try defaults first, then tune `--threads` / `--maxblocks`.
## Related samples
**blockArraySum** (atomics + grid-stride) → **reduction** (two-stage shared memory) → **this sample** (single kernel + `grid.sync()`).
## Further reading
- [CUDA Cooperative Groups](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cooperative-groups)
- [Reduction whitepaper (PDF)](https://developer.download.nvidia.com/assets/cuda/files/reduction.pdf)
## Files
`reductionMultiBlockCG.py` · `requirements.txt` · `README.md`

View File

@@ -0,0 +1,472 @@
# 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
# distribution 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.
"""
Single-Pass Multi-Block Reduction with Cooperative Groups
Demonstrates single-kernel multi-stage reduction using grid-wide
synchronization. Traditional reduction needs multiple kernel launches,
but with grid.sync() from Cooperative Groups, we can complete all
stages in ONE kernel.
Key Features:
- Grid-wide synchronization with grid.sync()
- Two-stage reduction in a single kernel (no atomic operations)
- Requires compute capability 6.0+ and cooperative launch
- Achieves 400-700 GB/s on modern GPUs
How it differs from other samples:
- blockArraySum.py: Basic thread/block indexing + atomicAdd
- reduction.py: High-performance shared memory, two-kernel approach
- This sample: Single-kernel multi-stage with grid.sync()
Transfers use CuPy on the same CUDA stream as ``launch()`` (``Stream.from_external``),
not ``cuda.bindings.driver`` memcpy. GPU timing uses CUDA events.
"""
import argparse
import os
import sys
import time
try:
import cupy as cp
import numpy as np
from cuda.core import (
Device,
EventOptions,
LaunchConfig,
Program,
ProgramOptions,
launch,
)
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install dependencies:")
print(" pip install -r requirements.txt")
sys.exit(1)
# Import utilities
utilities_path = os.path.join(os.path.dirname(__file__), "..", "..", "Utilities")
sys.path.insert(0, utilities_path)
from cuda_samples_utils import verify_array_result # noqa: E402
def _validate_threads_arg(threads):
if threads is None:
return None
if threads <= 0 or threads > 1024:
return "threads must be between 1 and 1024"
if (threads & (threads - 1)) != 0:
return (
"threads must be a power of 2 "
"(required by the shared-memory tree reduction kernel)"
)
return None
# Single-pass multi-block reduction kernel with grid-wide sync
REDUCTION_KERNEL = """
/*
* Single-Kernel Multi-Stage Reduction using grid.sync()
*
* Strategy:
* Stage 1: Each block reduces its portion → partial sum
* grid.sync() ← KEY: All blocks synchronize
* Stage 2: Block 0 reduces all partial sums → final result
*
* Key feature: grid.sync() enables multi-stage within ONE kernel
*/
#include <cooperative_groups.h>
namespace cg = cooperative_groups;
extern "C" __global__ void reduceSinglePassMultiBlockCG(
const float *__restrict__ g_idata,
float *__restrict__ g_odata,
unsigned int n)
{
cg::thread_block cta = cg::this_thread_block();
cg::grid_group grid = cg::this_grid();
extern __shared__ float sdata[];
unsigned int tid = threadIdx.x;
unsigned int blockSize = blockDim.x;
// Stage 1: Grid-stride loop + block reduction
float sum = 0.0f;
for (unsigned int i = grid.thread_rank(); i < n; i += grid.size()) {
sum += g_idata[i];
}
sdata[tid] = sum;
cg::sync(cta);
// Block reduction (sequential addressing)
for (unsigned int s = blockSize / 2; s > 0; s >>= 1) {
if (tid < s) {
sdata[tid] += sdata[tid + s];
}
cg::sync(cta);
}
if (tid == 0) {
g_odata[blockIdx.x] = sdata[0];
}
// KEY: Grid-wide synchronization (all blocks wait here)
grid.sync();
// Stage 2: Block 0 reduces all partial sums → final result
// Use a stride loop so all gridDim.x partial sums are covered even
// when gridDim.x > blockDim.x.
if (blockIdx.x == 0) {
// mySum stays 0.0f when tid >= gridDim.x (loop never executes),
// implicitly zero-filling sdata for threads beyond the partial-sum count.
float mySum = 0.0f;
for (unsigned int i = tid; i < gridDim.x; i += blockSize) {
mySum += g_odata[i];
}
sdata[tid] = mySum;
cg::sync(cta);
for (unsigned int s = blockSize / 2; s > 0; s >>= 1) {
if (tid < s) {
sdata[tid] += sdata[tid + s];
}
cg::sync(cta);
}
if (tid == 0) {
g_odata[0] = sdata[0];
}
}
}
"""
def get_max_cooperative_blocks(device, kernel, threads_per_block, shared_mem_bytes):
"""
Calculate max blocks for cooperative launch (all must be resident).
This is a conservative estimate that ignores shared memory limits;
for precise tuning, use cudaOccupancyMaxActiveBlocksPerMultiprocessor.
"""
# Get device properties
prop = device.properties
# Calculate maximum blocks per SM
# Note: We use cudaOccupancyMaxActiveBlocksPerMultiprocessor functionality
# For simplicity in Python, we'll use a conservative estimate
num_sms = prop.multiprocessor_count
max_threads_per_sm = prop.max_threads_per_multiprocessor
max_blocks_per_sm = max_threads_per_sm // threads_per_block
# Total blocks = blocks per SM × number of SMs
max_blocks = max_blocks_per_sm * num_sms
# Also respect max_grid_dim_x
max_blocks = min(max_blocks, prop.max_grid_dim_x)
return max_blocks
def run(
num_elements=1 << 25,
max_threads=None,
max_blocks=None,
test_iterations=100,
cuda_include_dir=None,
):
"""Run single-pass multi-block reduction benchmark."""
if cuda_include_dir is None:
raise ValueError("cuda_include_dir is required")
print("\n" + "=" * 70)
print("Single-Pass Multi-Block Reduction with Cooperative Groups")
print("=" * 70)
msg = "Multi-stage reduction in a single kernel using grid.sync()"
print(f"\nDemonstrates: {msg}")
# Initialize device
device = Device()
device.set_current()
major, minor = device.compute_capability
print("\nDevice Information:")
print(f" Name: {device.name}")
print(f" Compute Capability: sm_{major}.{minor}")
# Get device properties for configuration
prop = device.properties
# Determine threads per block
if max_threads is None:
max_threads = prop.max_threads_per_block
threads_per_block = min(max_threads, 1024)
# Define data type and itemsize
itemsize = np.dtype(np.float32).itemsize
print("\nReduction Configuration:")
print(f" Number of elements: {num_elements:,}")
print(f" Data size: {num_elements * itemsize / (1024**2):.2f} MB")
# Compile kernel
print("\nCompiling CUDA kernel...")
# Support colon-separated multiple include paths
include_paths = cuda_include_dir.split(":")
program_options = ProgramOptions(
std="c++17", arch=f"sm_{device.arch}", include_path=include_paths
)
prog = Program(REDUCTION_KERNEL, code_type="c++", options=program_options)
mod = prog.compile("cubin")
kernel = mod.get_kernel("reduceSinglePassMultiBlockCG")
print(" Kernel compiled successfully")
# Calculate blocks for cooperative launch
shared_mem_bytes = threads_per_block * itemsize
if max_blocks is None:
max_blocks = get_max_cooperative_blocks(
device, kernel, threads_per_block, shared_mem_bytes
)
# Calculate optimal blocks (all must be resident)
num_blocks = min(
max_blocks, (num_elements + threads_per_block - 1) // threads_per_block
)
print("\nLaunch Configuration:")
print(f" Threads per block: {threads_per_block}")
print(f" Number of blocks: {num_blocks}")
print(f" Total threads: {num_blocks * threads_per_block:,}")
print(f" Shared memory per block: {shared_mem_bytes} bytes")
print(" Launch mode: Cooperative (grid-wide sync enabled)")
# Generate random input data
print("\n> Generating random input data...")
rng = np.random.default_rng(42)
h_idata = (rng.random(num_elements) * 256).astype(np.float32)
stream = device.create_stream()
cp_stream = cp.cuda.Stream.from_external(stream)
try:
d_odata = cp.empty(num_blocks, dtype=np.float32)
with cp_stream:
d_idata = cp.asarray(h_idata, dtype=np.float32)
stream.sync()
# Compute CPU reference
print("> Computing reference result on CPU...")
cpu_start = time.perf_counter()
cpu_result = float(np.sum(h_idata))
cpu_time = time.perf_counter() - cpu_start
print(f" CPU time: {cpu_time:.6f} seconds")
# Warm-up
print("\n> Warming up GPU...")
launch_config = LaunchConfig(
grid=(num_blocks, 1, 1),
block=(threads_per_block, 1, 1),
shmem_size=shared_mem_bytes,
cooperative_launch=True,
)
n_u32 = np.uint32(num_elements)
ptr_in = d_idata.data.ptr
ptr_out = d_odata.data.ptr
try:
launch(stream, launch_config, kernel, ptr_in, ptr_out, n_u32)
except Exception as e:
print(f" Cooperative launch failed: {e}")
return 1
stream.sync()
print(" Warm-up successful")
# Benchmark (CUDA events — not host wall clock around the whole loop)
print(f"\n> Running benchmark ({test_iterations} iterations)...")
event_options = EventOptions(enable_timing=True)
start_event = stream.device.create_event(options=event_options)
end_event = stream.device.create_event(options=event_options)
# cuda.core event elapsed time (end - start) is in milliseconds (CUDA API).
gpu_times_ms = []
for _ in range(test_iterations):
try:
stream.record(start_event)
launch(stream, launch_config, kernel, ptr_in, ptr_out, n_u32)
stream.record(end_event)
end_event.sync()
gpu_times_ms.append(float(end_event - start_event))
except Exception as e:
print(f"Benchmark iteration failed: {e}")
return 1
avg_gpu_ms = float(np.mean(gpu_times_ms))
avg_gpu_s = avg_gpu_ms / 1000.0
stream.sync()
with cp_stream:
h_result = cp.asnumpy(d_odata[:1])
gpu_result = float(h_result[0])
# Performance metrics use seconds for throughput and speedup.
# CPU time is already in seconds.
bytes_processed = num_elements * 4
throughput_gb_s = bytes_processed / avg_gpu_s / 1e9
print("\n> Performance Results:")
print(f" Average GPU time: {avg_gpu_ms:.6f} ms")
print(f" Throughput: {throughput_gb_s:.2f} GB/s")
print(f" Speedup vs CPU: {cpu_time / avg_gpu_s:.2f}x")
# Validate results
print("\n> Validating results...")
success = verify_array_result(
np.array([gpu_result]),
np.array([cpu_result]),
rtol=1e-5,
atol=1e-5,
)
# Summary
print("\n" + "=" * 70)
print("Summary")
print("=" * 70)
print(f"""
Single-kernel two-stage reduction:
Stage 1: {num_blocks} blocks → {num_blocks} partial sums
grid.sync() ← All blocks synchronize (KEY innovation)
Stage 2: Block 0 → 1 final result
Total: 1 kernel launch, {throughput_gb_s:.2f} GB/s
Comparison:
• Traditional: 2 kernel launches or kernel + CPU
• This sample: 1 kernel with grid.sync() between stages
• Benefit: Eliminates ~5-20μs launch overhead per stage
""")
print("=" * 70)
if success:
print("Single-Pass Multi-Block Reduction completed successfully!")
else:
print("Single-Pass Multi-Block Reduction FAILED!")
print("=" * 70 + "\n")
return 0 if success else 1
finally:
stream.close()
def main():
"""Main entry point with argument parsing."""
parser = argparse.ArgumentParser(
description="Single-Pass Multi-Block Reduction with Cooperative Groups"
)
parser.add_argument(
"--n",
type=int,
default=1 << 25,
help="Number of elements to reduce (default: 33554432 = 2^25)",
)
parser.add_argument(
"--threads",
type=int,
default=None,
help=(
"Threads per block, power of 2 in [1, 1024]; "
"default: device maximum (typically 1024)"
),
)
parser.add_argument(
"--maxblocks",
type=int,
default=None,
help=(
"Maximum number of blocks "
"(default: auto-calculated for cooperative launch)"
),
)
parser.add_argument(
"--iterations",
type=int,
default=100,
help="Number of benchmark iterations (default: 100)",
)
parser.add_argument(
"--cuda-include-dir",
type=str,
required=True,
help=(
"CUDA include directory for NVRTC "
"(can use colon-separated paths, e.g., /path1:/path2)"
),
)
args = parser.parse_args()
# Validate arguments
if args.n <= 0:
print("Error: n must be positive")
return 1
err_threads = _validate_threads_arg(args.threads)
if err_threads:
print(f"Error: {err_threads}")
return 1
if args.maxblocks is not None and args.maxblocks <= 0:
print("Error: maxblocks must be positive")
return 1
try:
exit_code = run(
num_elements=args.n,
max_threads=args.threads,
max_blocks=args.maxblocks,
test_iterations=args.iterations,
cuda_include_dir=args.cuda_include_dir,
)
sys.exit(exit_code)
except Exception as e:
print(f"\nError: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,10 @@
# Python Multi-Block Cooperative Reduction Sample Requirements
# Install with: pip install -r requirements.txt
numpy>=2.3.2
cuda-core>=0.6.0
cuda-python>=13.0.0
# Headers for NVRTC: cooperative_groups.h includes cuda/std/* (CCCL)
cuda-cccl>=1.0.0
# Use cupy-cuda13x>=14.0.0 for cp.cuda.Stream.from_external(stream)
cupy-cuda13x>=14.0.0

View File

@@ -0,0 +1,141 @@
# Sample: simpleZeroCopy (Python)
## Description
This sample demonstrates zero-copy access using **`cuda.core`** to compile and launch a kernel, and **`cuda.bindings.runtime`** for mapped pinned host memory (`cudaHostAlloc` with `cudaHostAllocMapped`, `cudaHostGetDevicePointer`, and `cudaFreeHost`). The GPU loads and stores through **device** addresses that refer to that host memory—no `cudaMemcpy` in or out. The example is vector add with inputs and output as NumPy views of the host side of those buffers.
## What you will learn
- How to allocate **mapped** pinned host memory with `cudaHostAlloc` (via `cuda.bindings.runtime`) so the GPU can use `cudaHostGetDevicePointer` addresses in a kernel
- How `cuda.core.PinnedMemoryResource` differs (staging/copies; not guaranteed to be `cudaHostAllocMapped` for direct kernel access)
- How to build NumPy views of host addresses with `ctypes` and `numpy.frombuffer`
- How to launch CUDA kernels with `cuda.core`’s `Program` and `launch`, passing **device** pointers for mapped buffers
- When zero-copy is beneficial vs. device memory with explicit transfers
- How to validate results on the host without a D2H memcpy
## Key libraries
- `numpy` – CPU arrays and reference computation
- `cuda-core` – `Device`, stream, `Program`, `LaunchConfig`, `launch`
- `cuda-python` (`cuda.bindings.runtime`) – `cudaHostAlloc` / `cudaHostGetDevicePointer` / `cudaFreeHost` for mapped host memory
## Key APIs
**From cuda.core:** `Device`, `device.create_stream()`, `Program`, `ProgramOptions`, `LaunchConfig`, `launch`
**From cuda.bindings.runtime:** `cudaHostAlloc` (with `cudaHostAllocMapped` | `cudaHostAllocPortable`), `cudaHostGetDevicePointer`, `cudaFreeHost`
**From the standard library:** `ctypes` – wrap host pointers for `numpy.frombuffer` float32 views
**Memory management:** Free host memory with `cudaFreeHost` in a `finally` block; call `stream.close()` when done.
## Zero-Copy Memory: When to Use
### Benefits
- **No explicit transfers**: Simplifies code by eliminating `cudaMemcpy` calls
- **Automatic synchronization**: Host can access results immediately after kernel completes
- **Good for small data**: Overhead of explicit transfers can exceed benefits for small arrays
- **Excellent for integrated GPUs**: On systems like Jetson (Tegra), CPU and GPU share physical memory
### Limitations
- **Slower access**: Limited by PCIe bandwidth vs. device memory bandwidth
- **Not for compute-intensive**: Device memory is much faster for frequently accessed data
- **Discrete GPU overhead**: Each access crosses PCIe bus
### Best Use Cases
1. Small data sets where transfer overhead dominates
2. Data accessed infrequently by GPU
3. Integrated GPU platforms (shared memory)
4. Streaming data from host to device
5. Prototyping and debugging (simplifies memory management)
## Requirements
1. **NVIDIA GPU** and a **driver** compatible with your installed `cuda-python` / `cuda-core` wheels.
2. **Python 3.10 or newer**
3. Install **`pip install -r requirements.txt`** (NumPy, `cuda-python`, `cuda-core`). A **system** CUDA Toolkit is not strictly required if the process can load the driver/runtime; use `LD_LIBRARY_PATH` in *How to run* if you hit missing-library errors.
**Install packages:**
```bash
pip install -r requirements.txt
```
Or manually:
```bash
pip install numpy>=2.3.2 cuda-core>=0.6.0 cuda-python>=13.0.0
```
## How to run
Basic usage:
```bash
# Pre-steps: Set library path
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
# Run with default parameters (1M elements)
python simpleZeroCopy.py
```
With custom parameters:
```bash
# Use 2M elements
python simpleZeroCopy.py --num_elements 2097152
# Show help
python simpleZeroCopy.py --help
```
### Command line arguments
- `--num_elements`: Number of elements in vectors (default: 1048576)
- Each vector uses `num_elements * 4 bytes` (float32)
- Default: ~4 MB per vector, ~12 MB total
## Expected Output
Device name and compute capability **depend on your system**; the rest of the log should match this shape when validation passes.
```
======================================================================
simpleZeroCopy - CUDA Python Sample
======================================================================
Device Information:
Name: <your GPU>
Compute Capability: <major>.<minor>
> Memory: mapped pinned host (cudaHostAlloc + cudaHostGetDevicePointer)
Compiling CUDA kernel...
Kernel compiled successfully
Allocating memory:
Vector size: 1,048,576 elements
Memory per vector: 4.00 MB
Total memory: 12.00 MB
> Allocating mapped pinned host memory...
Mapped host memory allocated successfully
> Initializing vectors on host...
> Computing reference result on CPU...
> Launching vectorAddGPU kernel...
Note: GPU accesses host memory directly (zero-copy)
Kernel execution complete
> Checking results from vectorAddGPU()...
Comparing 1,048,576 elements...
Relative error: 0.000000e+00
Validation PASSED
======================================================================
simpleZeroCopy completed successfully!
======================================================================
```
## Files
- `simpleZeroCopy.py` – Main Python implementation
- `README.md` – This file
- `requirements.txt` – Python package dependencies

View File

@@ -0,0 +1,6 @@
# simpleZeroCopy - Requirements
# Install with: pip install -r requirements.txt
numpy>=2.3.2
cuda-python>=13.0.0
cuda-core>=0.6.0

View File

@@ -0,0 +1,275 @@
# 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
# distribution 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.
import argparse
import ctypes
import sys
from pathlib import Path
try:
import numpy as np
from cuda.bindings import runtime as cuda_rt
from cuda.core import (
Device,
LaunchConfig,
Program,
ProgramOptions,
launch,
)
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
# Add parent directory to path to import utilities
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
def _mapped_host_alloc(num_floats, stream):
"""
Allocate page-locked host memory mapped for device access; return
(host_ptr, device_ptr) for CPU views and for ``launch()``.
"""
nbytes = int(num_floats) * np.dtype(np.float32).itemsize
if nbytes <= 0:
return 0, 0
err, h_ptr = cuda_rt.cudaHostAlloc(
nbytes, cuda_rt.cudaHostAllocMapped | cuda_rt.cudaHostAllocPortable
)
if err != cuda_rt.cudaError_t.cudaSuccess:
raise RuntimeError(f"cudaHostAlloc failed: {err}")
err, d_ptr = cuda_rt.cudaHostGetDevicePointer(h_ptr, 0)
if err != cuda_rt.cudaError_t.cudaSuccess:
cuda_rt.cudaFreeHost(h_ptr)
raise RuntimeError(f"cudaHostGetDevicePointer failed: {err}")
# Ensure prior work on this stream is visible before host fills buffers.
if stream is not None:
stream.sync()
return h_ptr, d_ptr
def _float_view(host_ptr, num_floats):
return np.frombuffer(
(ctypes.c_float * num_floats).from_address(host_ptr),
dtype=np.float32,
count=num_floats,
)
# CUDA C++: vector add with grid-stride loop
VECTOR_ADD_KERNEL = """
extern "C" __global__
void vectorAddGPU(float* c, const float* a, const float* b, int N) {
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t stride = gridDim.x * blockDim.x;
for (size_t i = tid; i < N; i += stride) {
c[i] = a[i] + b[i];
}
}
"""
def run(num_elements=1048576):
"""
Zero-copy vector add: map host memory, launch kernel with device
pointers, validate on CPU.
This function shows how to:
1. Allocate pinned (page-locked) host memory
2. Map host memory into GPU address space (zero-copy)
3. Access host memory directly from GPU kernel
4. Validate results
Parameters
----------
num_elements : int
Number of elements in vectors (default: 1048576)
"""
print("\n" + "=" * 70)
print("simpleZeroCopy - CUDA Python Sample")
print("=" * 70)
# Initialize device
device = Device()
device.set_current()
major, minor = device.compute_capability
print("\nDevice Information:")
print(f" Name: {device.name}")
print(f" Compute Capability: {major}.{minor}")
# Create stream
stream = device.create_stream()
mapped_host_ptrs = []
try:
print(
"\n> Memory: mapped pinned host "
"(cudaHostAlloc + cudaHostGetDevicePointer)"
)
print("\nCompiling CUDA kernel...")
program_options = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
prog = Program(VECTOR_ADD_KERNEL, code_type="c++", options=program_options)
mod = prog.compile("cubin")
kernel = mod.get_kernel("vectorAddGPU")
print(" Kernel compiled successfully")
bytes_total = num_elements * np.dtype(np.float32).itemsize
print("\nAllocating memory:")
print(f" Vector size: {num_elements:,} elements")
print(f" Memory per vector: {bytes_total / (1024**2):.2f} MB")
print(f" Total memory: {3 * bytes_total / (1024**2):.2f} MB")
print("\n> Allocating mapped pinned host memory...")
h_a, d_a = _mapped_host_alloc(num_elements, stream)
mapped_host_ptrs.append(h_a)
h_b, d_b = _mapped_host_alloc(num_elements, stream)
mapped_host_ptrs.append(h_b)
h_c, d_c = _mapped_host_alloc(num_elements, stream)
mapped_host_ptrs.append(h_c)
a = _float_view(h_a, num_elements)
b = _float_view(h_b, num_elements)
c = _float_view(h_c, num_elements)
print(" Mapped host memory allocated successfully")
print("\n> Initializing vectors on host...")
rng = np.random.default_rng(42)
a[:] = rng.random(num_elements).astype(np.float32)
b[:] = rng.random(num_elements).astype(np.float32)
c[:] = 0
print("> Computing reference result on CPU...")
reference = a + b
print("\n> Launching vectorAddGPU kernel...")
print(" Note: GPU accesses host memory directly (zero-copy)")
block_size = 256
grid_size = (num_elements + block_size - 1) // block_size
config = LaunchConfig(grid=grid_size, block=block_size)
# Pass device pointers from cudaHostGetDevicePointer, not raw host VAs.
launch(
stream,
config,
kernel,
int(d_c),
int(d_a),
int(d_b),
np.int32(num_elements),
)
stream.sync()
print(" Kernel execution complete")
print("\n> Checking results from vectorAddGPU()...")
print(f" Comparing {num_elements:,} elements...")
# ``c`` is a host view of the same buffer; no cudaMemcpy D2H needed.
if np.allclose(c, reference, rtol=1e-5, atol=1e-6):
error_norm = np.linalg.norm(c - reference)
ref_norm = np.linalg.norm(reference)
relative_error = error_norm / ref_norm
print(f" Relative error: {relative_error:.6e}")
print(" Validation PASSED")
success = True
else:
max_error = np.max(np.abs(c - reference))
print(f" Max error: {max_error}")
print(" Validation FAILED")
success = False
print("\n" + "=" * 70)
if success:
print("simpleZeroCopy completed successfully!")
else:
print("simpleZeroCopy FAILED!")
print("=" * 70 + "\n")
return 0 if success else 1
finally:
for h in reversed(mapped_host_ptrs):
if h:
cuda_rt.cudaFreeHost(h)
stream.close()
def main():
"""Parse CLI, call ``run()``, and exit with validation status."""
parser = argparse.ArgumentParser(
description="Demonstrate zero-copy memory access with CUDA",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python simpleZeroCopy.py
python simpleZeroCopy.py --num_elements 2097152
What is Zero-Copy Memory?
Zero-copy allows the GPU to directly access host (CPU) memory without
explicit memory transfers. This is useful for:
- Small data that doesn't benefit from explicit transfers
- Data that is accessed infrequently
- Integrated GPUs that share memory with CPU
Trade-offs:
- Slower than device memory (PCIe bandwidth limited)
- No explicit transfers needed (simpler code)
- Good for discrete GPUs with small data
- Excellent for integrated GPUs (e.g., Tegra)
""",
)
parser.add_argument(
"--num_elements",
type=int,
default=1048576,
help="Number of elements in vectors (default: 1048576)",
)
args = parser.parse_args()
if args.num_elements <= 0:
print("Error: num_elements must be positive")
sys.exit(1)
try:
exit_code = run(num_elements=args.num_elements)
except Exception as e:
print(f"\nError: {e}")
import traceback
traceback.print_exc()
exit_code = 1
sys.exit(exit_code)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,98 @@
# Sample: Streaming Copy + Compute Overlap (Python)
## Description
Demonstrate how to overlap memory transfers (H2D/D2H) with kernel computation using CUDA streams. This technique hides transfer latency and improves GPU utilization.
## What You'll Learn
- Using `PinnedMemoryResource` for async-capable host memory
- Using `DeviceMemoryResource` for GPU memory allocation
- Creating multiple streams with `Device.create_stream()`
- Async memory copies with `Buffer.copy_to()`
- Overlapping H2D transfers, kernel execution, and D2H transfers
## Key Concept
**Without overlap (sequential):**
```
[====H2D====][====Compute====][====D2H====]
```
**With overlap (multiple streams):**
```
Stream 0: [H2D][Compute][D2H]
Stream 1: [H2D][Compute][D2H]
Stream 2: [H2D][Compute][D2H]
```
## Key APIs (all from `cuda.core`)
- `Device` - Device management
- `Device.create_stream()` - Create CUDA streams
- `Stream.sync()` - Synchronize stream
- `PinnedMemoryResource` - Pinned host memory (required for async transfers)
- `DeviceMemoryResource` - GPU device memory
- `Buffer.copy_to(dst, stream=stream)` - Async memory copy
- `Program`, `LaunchConfig`, `launch` - Kernel compilation and execution
### From `numpy`:
- `np.from_dlpack()` - Zero-copy view of pinned memory buffers
## Requirements
- CUDA Toolkit 13.0+
- Python 3.10+
- `cuda-python`, `cuda-core`, `numpy`
## Installation
```bash
pip install -r requirements.txt
```
## How to Run
```bash
python streamingCopyComputeOverlap.py
```
## Expected Output
```
============================================================
Streaming Copy + Compute Overlap
Using pure cuda.core APIs
============================================================
Device: NVIDIA GeForce RTX XXXX
Kernel compiled ✓
Problem size: 16,000,000 elements (61 MB)
--- Sequential (no overlap) ---
Timeline: [H2D][Compute][D2H]
Time: X.XX ms (±X.XX)
--- Streamed (with overlap) ---
Stream 0: [H2D][Compute][D2H]
Stream 1: [H2D][Compute][D2H]
Stream 2: [H2D][Compute][D2H]
...
2 streams: X.XX ms (±X.XX) - speedup: X.XXx
4 streams: X.XX ms (±X.XX) - speedup: X.XXx
8 streams: X.XX ms (±X.XX) - speedup: X.XXx
============================================================
Key: Pinned memory + multiple streams = overlap transfers with compute
Note: Speedup depends on hardware characteristics. This technique
benefits most when transfer time is significant relative to compute.
============================================================
```
## See Also
- [cuda.core Documentation](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- [CUDA Streams Best Practices](https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html#overlapping-data-transfers)

View File

@@ -0,0 +1,6 @@
# Streaming Copy Compute Overlap Sample Requirements
# Requires Python 3.10 or newer
cuda-python>=13.0.0
cuda-core>=0.6.0
numpy>=2.3.2

View File

@@ -0,0 +1,312 @@
# 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.
"""
Streaming Copy + Compute Overlap
Demonstrates how to overlap memory transfers with kernel computation using
CUDA streams to maximize GPU utilization.
Uses pure cuda.core APIs:
- Device, Stream for device and stream management
- PinnedMemoryResource, DeviceMemoryResource for memory allocation
- Buffer.copy_to() for async memory copies
- Program, LaunchConfig, launch for kernel compilation and execution
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
try:
import numpy as np
from cuda.core import (
Device,
DeviceMemoryResource,
EventOptions,
LaunchConfig,
PinnedMemoryResource,
Program,
ProgramOptions,
launch,
)
from cuda_samples_utils import print_gpu_info
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Install with: pip install -r requirements.txt")
sys.exit(1)
# CUDA Kernel - compute-intensive vector operation (grid-stride loop)
VECTOR_SCALE_KERNEL = r"""
extern "C" __global__
void vector_scale(const float* input, float* output, float scale, size_t N) {
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t stride = (size_t)gridDim.x * blockDim.x;
for (size_t i = tid; i < N; i += stride) {
float val = input[i] * scale;
// Add compute work to make kernel non-trivial
for (int j = 0; j < 50; j++) {
val = sqrtf(val * val + 1.0f);
}
output[i] = val;
}
}
"""
def buffer_to_numpy(buffer, n_elements):
"""Create numpy view of cuda.core Buffer via DLPack."""
return np.from_dlpack(buffer).view(np.float32).reshape(n_elements)
def main():
print("=" * 60)
print("Streaming Copy + Compute Overlap")
print("Using pure cuda.core APIs")
print("=" * 60)
# Initialize device
device = Device(0)
device.set_current()
print()
print_gpu_info(device)
# Compile kernel
arch = f"sm_{device.arch}"
program = Program(
VECTOR_SCALE_KERNEL, code_type="c++", options=ProgramOptions(arch=arch)
)
kernel = program.compile(target_type="cubin").get_kernel("vector_scale")
print("Kernel compiled ✓")
# Parameters
N = 16_000_000 # 16M elements
n_bytes = N * 4
scale = 2.5
n_runs = 10
print(f"\nProblem size: {N:,} elements ({n_bytes / 1024 / 1024:.0f} MB)")
# Create memory resources
pinned_mr = PinnedMemoryResource()
device_mr = DeviceMemoryResource(device.device_id)
default_stream = device.create_stream()
# =========================================================================
# Sequential Execution
# =========================================================================
print("\n--- Sequential (no overlap) ---")
print("Timeline: [H2D][Compute][D2H]")
h_in = h_out = d_in = d_out = None
try:
# Pre-allocate buffers
h_in = pinned_mr.allocate(n_bytes, default_stream)
h_out = pinned_mr.allocate(n_bytes, default_stream)
d_in = device_mr.allocate(n_bytes, default_stream)
d_out = device_mr.allocate(n_bytes, default_stream)
# Sync before numpy access (numpy operations aren't stream ordered)
default_stream.sync()
# Initialize input
np_in = buffer_to_numpy(h_in, N)
np_in[:] = np.random.rand(N).astype(np.float32) * 100
config = LaunchConfig(grid=((N + 255) // 256,), block=(256,))
event_opts = EventOptions(enable_timing=True)
# Warm up
h_in.copy_to(d_in, stream=default_stream)
launch(
default_stream,
config,
kernel,
d_in,
d_out,
np.float32(scale),
np.uint64(N),
)
d_out.copy_to(h_out, stream=default_stream)
default_stream.sync()
# Benchmark with CUDA events
times = []
for _ in range(n_runs):
start_ev = device.create_event(options=event_opts)
end_ev = device.create_event(options=event_opts)
default_stream.record(start_ev)
h_in.copy_to(d_in, stream=default_stream) # Async H2D
launch(
default_stream,
config,
kernel,
d_in,
d_out,
np.float32(scale),
np.uint64(N),
)
d_out.copy_to(h_out, stream=default_stream) # Async D2H
default_stream.record(end_ev)
default_stream.sync()
times.append(end_ev - start_ev)
seq_time = np.mean(times)
print(f"Time: {seq_time:.2f} ms (±{np.std(times):.2f})")
# Verification: compute expected on CPU and compare
default_stream.sync()
np_out = buffer_to_numpy(h_out, N)
expected = np_in.astype(np.float32) * scale
for _ in range(50):
expected = np.sqrt(expected * expected + 1.0).astype(np.float32)
if np.allclose(np_out, expected, rtol=1e-4, atol=1e-4):
print("Verification: PASSED")
else:
print("Verification: FAILED")
finally:
for buf in (h_in, h_out, d_in, d_out):
if buf is not None:
buf.close()
default_stream.close()
# =========================================================================
# Streamed Execution
# =========================================================================
print("\n--- Streamed (with overlap) ---")
print("Stream 0: [H2D][Compute][D2H]")
print("Stream 1: [H2D][Compute][D2H]")
print("Stream 2: [H2D][Compute][D2H]")
print("...")
for n_streams in [2, 4, 8]:
chunk_size = N // n_streams
chunk_bytes = chunk_size * 4
# Create streams
streams = [device.create_stream() for _ in range(n_streams)]
# Pre-allocate per-stream buffers
h_ins, h_outs, d_ins, d_outs = [], [], [], []
try:
for i in range(n_streams):
h_ins.append(pinned_mr.allocate(chunk_bytes, streams[i]))
h_outs.append(pinned_mr.allocate(chunk_bytes, streams[i]))
d_ins.append(device_mr.allocate(chunk_bytes, streams[i]))
d_outs.append(device_mr.allocate(chunk_bytes, streams[i]))
# Initialize input data
for i in range(n_streams):
streams[i].sync()
np_view = buffer_to_numpy(h_ins[i], chunk_size)
np_view[:] = np.random.rand(chunk_size).astype(np.float32) * 100
chunk_config = LaunchConfig(grid=((chunk_size + 255) // 256,), block=(256,))
# Warm up
for i in range(n_streams):
h_ins[i].copy_to(d_ins[i], stream=streams[i])
launch(
streams[i],
chunk_config,
kernel,
d_ins[i],
d_outs[i],
np.float32(scale),
np.uint64(chunk_size),
)
d_outs[i].copy_to(h_outs[i], stream=streams[i])
for stream in streams:
stream.sync()
# Benchmark with CUDA events (use stream 0 for timing)
times = []
event_opts = EventOptions(enable_timing=True)
for _ in range(n_runs):
start_ev = device.create_event(options=event_opts)
end_ev = device.create_event(options=event_opts)
streams[0].record(start_ev)
# Issue all operations - they overlap across streams
for i in range(n_streams):
h_ins[i].copy_to(d_ins[i], stream=streams[i]) # Async H2D
launch(
streams[i],
chunk_config,
kernel,
d_ins[i],
d_outs[i],
np.float32(scale),
np.uint64(chunk_size),
)
d_outs[i].copy_to(h_outs[i], stream=streams[i]) # Async D2H
# Wait for all streams, record end on stream 0
for stream in streams:
stream.sync()
streams[0].record(end_ev)
streams[0].sync()
times.append(end_ev - start_ev)
avg = np.mean(times)
speedup = seq_time / avg
print(
f"{n_streams} streams: {avg:.2f} ms (±{np.std(times):.2f}) "
f"- speedup: {speedup:.2f}x"
)
# Verification (streamed): concatenate chunks and compare to expected
for s in streams:
s.sync()
out_chunks = [
buffer_to_numpy(h_outs[i], chunk_size) for i in range(n_streams)
]
in_chunks = [
buffer_to_numpy(h_ins[i], chunk_size) for i in range(n_streams)
]
np_out = np.concatenate(out_chunks)
np_in = np.concatenate(in_chunks)
expected = np_in.astype(np.float32) * scale
for _ in range(50):
expected = np.sqrt(expected * expected + 1.0).astype(np.float32)
if not np.allclose(np_out, expected, rtol=1e-4, atol=1e-4):
print(f" Verification: FAILED for {n_streams} streams")
finally:
for buf in h_ins + h_outs + d_ins + d_outs:
buf.close()
for s in streams:
s.close()
print("\n" + "=" * 60)
print("Key: Pinned memory + multiple streams = overlap transfers with compute")
print("\nNote: Speedup depends on hardware characteristics. This technique")
print("benefits most when transfer time is significant relative to compute.")
print("=" * 60)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,138 @@
# tmaTensorMap (Python)
## Description
This sample demonstrates how to use Tensor Memory Accelerator (TMA)
descriptors with `cuda.core` on Hopper and later GPUs (compute
capability >= 9.0). TMA enables efficient bulk data movement between
global and shared memory using hardware-managed tensor map
descriptors, which are a key building block for modern GEMM kernels
and large shared-memory tile loads.
The sample:
1. Creates a TMA tiled descriptor from a CuPy device array via
`StridedMemoryView.from_any_interface(...).as_tensor_map(...)`.
2. Passes the descriptor by value (as `__grid_constant__`) to a
kernel that uses libcudacxx TMA/barrier wrappers to bulk-load a
tile into shared memory, then copies it out to verify correctness.
3. Reuses the same descriptor against a new source tensor with
`replace_address()` to avoid rebuilding it.
## What You'll Learn
- Creating a TMA descriptor from a strided device tensor via
`StridedMemoryView.as_tensor_map(box_dim=...)`
- Passing a tensor map to a kernel by value using
`__grid_constant__`
- Using libcudacxx (`cuda/barrier`) to coordinate TMA loads with a
block-scoped barrier
- Reusing a descriptor against a new source buffer via
`tensor_map.replace_address(new_tensor)`
- Compiling a kernel to CUBIN for a specific target arch so Hopper
features are available
- Using `cuda.pathfinder` to locate the CUDA toolkit include directory
CCCL headers and libcudacxx
## Key Libraries
- [`cuda.core`](https://nvidia.github.io/cuda-python/cuda-core/latest/) - compilation, launching, and tensor-map helpers
- `cuda.pathfinder` - locate the CUDA toolkit include directory
- `cupy` - allocate and fill device tensors
- `numpy` - scalar kernel arguments
## Key APIs
### From `cuda.core`
- `StridedMemoryView.from_any_interface(tensor, stream_ptr=-1)` - build a typed view from any DLPack/CUDA-array-interface tensor
- `StridedMemoryView.as_tensor_map(box_dim=(...))` - produce a TMA descriptor for the given tile shape
- `tensor_map.replace_address(new_tensor)` - retarget an existing descriptor at a new tensor
- `Program(code, code_type="c++", options=ProgramOptions(std="c++17", arch="sm_90", include_path=[...]))` - compile a C++ kernel against libcudacxx
- `program.compile("cubin")` - produce a CUBIN so `__grid_constant__` and TMA intrinsics are fully supported
- `launch(stream, config, kernel, tensor_map, ...)` - pass the TMA descriptor as a kernel argument
### From `cuda.pathfinder`
- `get_cuda_path_or_home()` - return the detected CUDA toolkit root for locating `include/cccl`
### From `cuda_samples_utils`
- `print_gpu_info()` - print device name and compute capability
## Requirements
### Hardware
- NVIDIA Hopper or newer GPU with Compute Capability 9.0 or higher (H100, H200, B200, ...)
- On GPUs older than Hopper the sample exits cleanly without running the kernel
- Minimum GPU memory: 512 MB
### Software
- CUDA Toolkit 13.0 or newer with libcudacxx (cccl) headers
- Python 3.10 or newer
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## Installation
Install the required packages from `requirements.txt`:
```bash
cd /path/to/cuda-samples/python/2_CoreConcepts/tmaTensorMap
pip install -r requirements.txt
```
The `requirements.txt` installs:
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## How to Run
### Basic usage
```bash
cd cuda-samples/python/2_CoreConcepts/tmaTensorMap
python tmaTensorMap.py
```
### With custom parameters
```bash
# Larger tensor (must be a multiple of the 128-element tile)
python tmaTensorMap.py --elements 8192
# Use a specific GPU
python tmaTensorMap.py --device 1
```
## Expected Output
On a Hopper (sm_90) GPU:
```
Device: NVIDIA H100 PCIe
Compute Capability: 9.0
TMA copy verified: 1024 elements across 8 tiles
replace_address verified: descriptor reused with new source tensor
```
**Note:** Device name and compute capability will vary based on your GPU.
## Files
- `tmaTensorMap.py` - Python implementation using `cuda.core` TMA APIs
- `README.md` - This file
- `requirements.txt` - Sample dependencies
- `../../Utilities/cuda_samples_utils.py` - Common utilities (imported by this sample)
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [TMA in the CUDA C++ Programming Guide](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#tensor-memory-accelerator)
- [`cuda::barrier` reference](https://nvidia.github.io/cccl/libcudacxx/extended_api/synchronization_primitives/barrier.html)

View File

@@ -0,0 +1,4 @@
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0
numpy>=1.24.0

View File

@@ -0,0 +1,281 @@
# 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.
"""
TMA Tensor Map with cuda.core
This sample demonstrates how to use Tensor Memory Accelerator (TMA)
descriptors with cuda.core on Hopper and later GPUs (compute
capability >= 9.0). TMA enables efficient bulk data movement between
global and shared memory using hardware-managed tensor map
descriptors.
The sample:
1. Creates a TMA tiled descriptor from a CuPy device array via
``StridedMemoryView.from_any_interface(...).as_tensor_map(...)``.
2. Passes the descriptor by value (as ``__grid_constant__``) to a
kernel that uses libcudacxx TMA/barrier wrappers to bulk-load a
tile into shared memory.
3. Reuses the same descriptor against a new source tensor with
``replace_address()`` to avoid rebuilding it.
On GPUs older than Hopper (sm < 90), the sample prints a diagnostic
and exits cleanly.
Ported from ``cuda_core/examples/tma_tensor_map.py`` in the
`cuda-python` repository.
"""
import os
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
try:
import cupy as cp
import numpy as np
from cuda.core import (
Device,
LaunchConfig,
Program,
ProgramOptions,
StridedMemoryView,
launch,
)
from cuda.pathfinder import find_nvidia_header_directory, get_cuda_path_or_home
from cuda_samples_utils import print_gpu_info
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
TILE_SIZE = 128 # elements per tile, must match the kernel constant
KERNEL_SRC = r"""
#include <cuda/barrier>
// Minimal definition of the 128-byte opaque tensor-map struct.
struct __align__(64) TensorMap { unsigned long long opaque[16]; };
static constexpr int TILE_SIZE = 128;
using TmaBarrier = cuda::barrier<cuda::thread_scope_block>;
extern "C"
__global__ void tma_copy(
const __grid_constant__ TensorMap tensor_map,
float* output,
int N)
{
__shared__ __align__(128) float smem[TILE_SIZE];
__shared__ TmaBarrier bar;
const int tid = threadIdx.x;
const int tile_start = blockIdx.x * TILE_SIZE;
if (tid == 0)
{
init(&bar, 1);
}
__syncthreads();
if (tid == 0)
{
cuda::device::experimental::cp_async_bulk_tensor_1d_global_to_shared(
smem,
reinterpret_cast<const CUtensorMap*>(&tensor_map),
tile_start,
bar);
bar.wait(cuda::device::barrier_arrive_tx(bar, 1, TILE_SIZE * sizeof(float)));
}
__syncthreads();
if (tid < TILE_SIZE)
{
const int idx = tile_start + tid;
if (idx < N)
output[idx] = smem[tid];
}
}
"""
def _get_cccl_include_paths() -> list:
"""Locate the CUDA toolkit and libcudacxx (cccl) include directories.
``cuda.pathfinder.find_nvidia_header_directory`` searches pip-installed
CUDA packages, conda environments, and the standard system install
locations, so this works without requiring ``CUDA_PATH`` or
``CUDA_HOME``. ``get_cuda_path_or_home`` is used as a final fallback.
"""
include_path: list = []
# libcudacxx (cccl) - preferred, provides <cuda/barrier> used below.
try:
cccl_dir = find_nvidia_header_directory("cccl")
if cccl_dir and os.path.isdir(cccl_dir):
include_path.append(cccl_dir)
except Exception: # noqa: S110 - fallback probes continue below
pass
# CUDA runtime headers - needed for the CUtensorMap driver type.
try:
cudart_dir = find_nvidia_header_directory("cudart")
if (
cudart_dir
and os.path.isdir(cudart_dir)
and cudart_dir not in include_path
):
include_path.append(cudart_dir)
except Exception: # noqa: S110 - fallback probes continue below
pass
# Fallback: use CUDA_PATH / CUDA_HOME when pathfinder comes up empty.
if not include_path:
cuda_path = get_cuda_path_or_home()
if cuda_path is not None:
cuda_include = os.path.join(cuda_path, "include")
if os.path.isdir(cuda_include):
include_path.append(cuda_include)
cccl_include = os.path.join(cuda_include, "cccl")
if os.path.isdir(cccl_include):
include_path.insert(0, cccl_include)
if not include_path:
print(
"Could not locate CUDA toolkit headers.\n"
"Tried cuda.pathfinder (pip/conda/system installs) and "
"CUDA_PATH / CUDA_HOME; none succeeded.\n"
"Set CUDA_HOME to your toolkit root (containing include/cccl "
"and include/cuda_runtime.h) and retry.",
file=sys.stderr,
)
sys.exit(1)
return include_path
def main() -> int:
import argparse
parser = argparse.ArgumentParser(
description="Use a TMA tensor map to bulk-copy data on Hopper+ GPUs"
)
parser.add_argument(
"--elements",
type=int,
default=1024,
help="Total number of float32 elements (must be a multiple of 128)",
)
parser.add_argument("--device", type=int, default=0, help="CUDA device id")
args = parser.parse_args()
if args.elements % TILE_SIZE != 0:
print(f"--elements must be a multiple of TILE_SIZE={TILE_SIZE}")
return 1
dev = Device(args.device)
print_gpu_info(dev)
arch = dev.compute_capability
if arch < (9, 0):
print(
f"\nTMA requires compute capability >= 9.0 (Hopper or later); "
f"this device is {arch.major}.{arch.minor}. Exiting cleanly."
)
return 0
dev.set_current()
include_path = _get_cccl_include_paths()
# Compile with the CUBIN code type to target the exact device arch.
prog = Program(
KERNEL_SRC,
code_type="c++",
options=ProgramOptions(
std="c++17",
arch=f"sm_{dev.arch}",
include_path=include_path,
),
)
mod = prog.compile("cubin")
kernel = mod.get_kernel("tma_copy")
# (1) Prepare input data and verify the initial TMA copy.
n = args.elements
src = cp.arange(n, dtype=cp.float32)
output = cp.zeros(n, dtype=cp.float32)
dev.sync() # CuPy uses its own stream
tensor_map = StridedMemoryView.from_any_interface(
src, stream_ptr=-1
).as_tensor_map(box_dim=(TILE_SIZE,))
n_tiles = n // TILE_SIZE
config = LaunchConfig(grid=n_tiles, block=TILE_SIZE)
launch(
dev.default_stream,
config,
kernel,
tensor_map,
output.data.ptr,
np.int32(n),
)
dev.sync()
if not cp.array_equal(output, src):
print("TMA copy produced incorrect results")
return 1
print(f"TMA copy verified: {n} elements across {n_tiles} tiles")
# (2) Demonstrate replace_address() without rebuilding the descriptor.
replacement = cp.full(n, fill_value=42.0, dtype=cp.float32)
dev.sync()
tensor_map.replace_address(replacement)
output2 = cp.zeros(n, dtype=cp.float32)
launch(
dev.default_stream,
config,
kernel,
tensor_map,
output2.data.ptr,
np.int32(n),
)
dev.sync()
if not cp.array_equal(output2, replacement):
print("replace_address produced incorrect results")
return 1
print("replace_address verified: descriptor reused with new source tensor")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,54 @@
# Sample: PyTorch Custom GPU Operator
## Description
This sample demonstrates how to add a custom GPU operation to PyTorch using the `cuda.core` API. It implements a simple square operation (y = x²) to show the complete workflow from CUDA kernel to PyTorch integration with autograd support.
## Requirements
- NVIDIA GPU with Compute Capability 7.0+
- CUDA Toolkit 13.0+
- Python 3.10+
- PyTorch 2.0+
- cuda-python >= 13.0.0
- cuda-core >= 0.6.0
## Installation
```bash
cd python/3_FrameworkInterop/customPyTorchKernel
pip install -r requirements.txt
```
## How to Run
```bash
# Basic usage
python customPyTorchKernel.py
# Test with more elements
python customPyTorchKernel.py --size 1000000
# Use specific GPU
CUDA_VISIBLE_DEVICES=1 python customPyTorchKernel.py
```
## Expected Output
The sample runs three tests:
1. Forward pass correctness (y = x²)
2. Backward pass correctness (gradient computation)
3. Multi-dimensional tensor support
All tests should pass, confirming the custom operator works correctly with PyTorch's autograd system.
## Key Concepts
The sample demonstrates:
- Writing CUDA kernels with grid-stride loops
- Runtime kernel compilation with cuda.core
- PyTorch autograd integration via `torch.autograd.Function`
- Stream management using PyTorch's current stream
- Kernel caching for performance
The code is self-documenting with inline comments explaining each step.

View File

@@ -0,0 +1,390 @@
# 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
# distribution 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.
"""
PyTorch Custom GPU Operator using cuda.core
Question: How do I add a custom GPU op to PyTorch?
Answer: This sample shows the complete workflow.
This sample implements a custom square operation (y = x²) to demonstrate:
- Writing a CUDA kernel
- Compiling with cuda.core
- Integrating with PyTorch's autograd system
- Proper device and stream management
"""
import sys
try:
import torch
from cuda.core import Device, LaunchConfig, Program, ProgramOptions, launch
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install: pip install torch cuda-python cuda-core")
sys.exit(1)
# ============================================================================
# Step 1: Define CUDA Kernel
# ============================================================================
# Simple element-wise square: y = x²
# This kernel is easy to understand and verify
SQUARE_KERNEL = """
extern "C" __global__
void square_kernel(const float* x, float* y, int n)
{
const unsigned int tid = threadIdx.x + blockIdx.x * blockDim.x;
for (int i = tid; i < n; i += gridDim.x * blockDim.x) {
y[i] = x[i] * x[i];
}
}
"""
# ============================================================================
# PyTorch Stream Wrapper
# ============================================================================
# cuda.core requires objects with __cuda_stream__ protocol
class PyTorchStreamWrapper:
def __init__(self, pt_stream):
self.pt_stream = pt_stream
def __cuda_stream__(self):
stream_id = self.pt_stream.cuda_stream
return (0, stream_id) # Return format required by CUDA Python
# ============================================================================
# Step 2: Kernel Compilation and Caching
# ============================================================================
# Compile kernel once per device and cache it to avoid recompilation overhead
# In real training loops, this avoids paying compilation cost on every forward.
_kernel_cache = {}
def get_square_kernel(device):
"""
Get or compile the square kernel for a given device.
Parameters
----------
device : Device
CUDA device object
Returns
-------
Kernel
Compiled CUDA kernel
"""
# Cache key based on device to avoid recompiling for the same GPU
key = device.pci_bus_id
if key not in _kernel_cache:
# Compile the kernel with appropriate architecture
opts = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
prog = Program(SQUARE_KERNEL, code_type="c++", options=opts)
mod = prog.compile("cubin")
_kernel_cache[key] = mod.get_kernel("square_kernel")
return _kernel_cache[key]
# ============================================================================
# Step 3: PyTorch Autograd Function
# ============================================================================
# This integrates the CUDA kernel with PyTorch's automatic differentiation
class SquareOp(torch.autograd.Function):
"""
Custom square operation using cuda.core.
Forward: y = x² (computed with custom CUDA kernel)
Backward: grad_x = 2 * x * grad_y (computed with PyTorch)
"""
@staticmethod
def forward(ctx, x):
"""
Forward pass: compute y = x² using custom CUDA kernel.
Parameters
----------
ctx : Context
PyTorch context for saving tensors
x : torch.Tensor
Input tensor (must be CUDA, float32, contiguous)
Returns
-------
torch.Tensor
Output tensor with y = x²
"""
# Validate input requirements
if not x.is_cuda:
raise RuntimeError("SquareOp only supports CUDA tensors")
if x.dtype != torch.float32:
raise RuntimeError("SquareOp only supports float32 tensors")
# Ensure contiguous memory layout for efficient kernel access
x = x.contiguous()
device = Device()
# Use PyTorch's current stream to ensure proper ordering with other PyTorch ops
# Create a cuda.core Stream from PyTorch's stream wrapper
torch_stream = torch.cuda.current_stream(device=x.device)
stream = device.create_stream(PyTorchStreamWrapper(torch_stream))
# Create a try/finally block to ensure the stream is properly closed
try:
# Get compiled kernel (cached)
kernel = get_square_kernel(device)
# Allocate output tensor
y = torch.empty_like(x)
# Configure kernel launch
n = int(x.numel())
threads_per_block = 256
blocks_per_grid = (n + threads_per_block - 1) // threads_per_block
config = LaunchConfig(grid=blocks_per_grid, block=threads_per_block)
# Launch the kernel
launch(stream, config, kernel, x.data_ptr(), y.data_ptr(), n)
finally:
# Ensure stream is properly closed
stream.close()
# Save input for backward pass
ctx.save_for_backward(x)
return y
@staticmethod
def backward(ctx, grad_output):
"""
Backward pass: compute gradient.
For y = x², the derivative is dy/dx = 2x
Therefore: grad_x = grad_output * 2x
Parameters
----------
ctx : Context
PyTorch context with saved tensors
grad_output : torch.Tensor
Gradient from upstream
Returns
-------
torch.Tensor
Gradient with respect to input
"""
# Retrieve saved input
(x,) = ctx.saved_tensors
# Note: We assume grad_output has the same dtype and device as x.
# This is guaranteed by PyTorch's autograd system.
# Compute gradient: d(x²)/dx = 2x
grad_x = 2.0 * x * grad_output
return grad_x
# ============================================================================
# Step 4: Public API
# ============================================================================
def square(x):
"""
Apply element-wise square operation using custom CUDA kernel.
Parameters
----------
x : torch.Tensor
Input tensor (must be on CUDA device, dtype=float32)
Returns
-------
torch.Tensor
Output tensor with y = x²
Examples
--------
>>> x = torch.randn(100, device='cuda')
>>> y = square(x)
>>> assert torch.allclose(y, x ** 2)
"""
return SquareOp.apply(x)
# ============================================================================
# Step 5: Testing and Verification
# ============================================================================
def main():
"""Test the custom square operation."""
import argparse
parser = argparse.ArgumentParser(
description="Custom PyTorch Square Operator using cuda.core"
)
parser.add_argument(
"--size", type=int, default=10000, help="Number of elements (default: 10000)"
)
args = parser.parse_args()
# Device info
device = Device()
device.set_current()
major, minor = device.compute_capability
print("\nDevice Information:")
print(f" Name: {device.name}")
print(f" Compute Capability: sm_{major}{minor}")
print("\n" + "=" * 70)
print("Custom PyTorch Square Operator Test")
print("=" * 70)
# ========================================================================
# Test 1: Forward Pass Correctness
# ========================================================================
print("\n" + "-" * 70)
print("Test 1: Forward Pass")
print("-" * 70)
x = torch.randn(args.size, dtype=torch.float32, device="cuda")
# Custom square operation
y_custom = square(x)
# PyTorch reference
y_reference = x**2
# Check correctness
max_error = torch.max(torch.abs(y_custom - y_reference)).item()
print(f"Max absolute error: {max_error:.2e}")
if torch.allclose(y_custom, y_reference, rtol=1e-5, atol=1e-6):
print("[PASS] Forward pass PASSED")
else:
print("[FAIL] Forward pass FAILED")
return 1
# ========================================================================
# Test 2: Backward Pass (Gradient) Correctness
# ========================================================================
print("\n" + "-" * 70)
print("Test 2: Backward Pass")
print("-" * 70)
# Test with requires_grad
x_custom = torch.randn(
args.size, dtype=torch.float32, device="cuda", requires_grad=True
)
x_reference = x_custom.clone().detach().requires_grad_(True)
# Forward pass
y_custom = square(x_custom)
y_reference = x_reference**2
# Create upstream gradient
grad_output = torch.randn_like(y_custom)
# Backward pass
y_custom.backward(grad_output)
y_reference.backward(grad_output)
# Check gradients
max_grad_error = torch.max(torch.abs(x_custom.grad - x_reference.grad)).item()
print(f"Max gradient error: {max_grad_error:.2e}")
if torch.allclose(x_custom.grad, x_reference.grad, rtol=1e-5, atol=1e-6):
print("[PASS] Backward pass PASSED")
else:
print("[FAIL] Backward pass FAILED")
return 1
# ========================================================================
# Test 3: Multi-dimensional Tensors
# ========================================================================
print("\n" + "-" * 70)
print("Test 3: Multi-dimensional Tensors")
print("-" * 70)
# Test with 2D tensor
x_2d = torch.randn(100, 100, dtype=torch.float32, device="cuda")
y_2d_custom = square(x_2d)
y_2d_reference = x_2d**2
if torch.allclose(y_2d_custom, y_2d_reference, rtol=1e-5, atol=1e-6):
print("[PASS] 2D tensor test PASSED")
else:
print("[FAIL] 2D tensor test FAILED")
return 1
# Test with 3D tensor
x_3d = torch.randn(10, 20, 30, dtype=torch.float32, device="cuda")
y_3d_custom = square(x_3d)
y_3d_reference = x_3d**2
if torch.allclose(y_3d_custom, y_3d_reference, rtol=1e-5, atol=1e-6):
print("[PASS] 3D tensor test PASSED")
else:
print("[FAIL] 3D tensor test FAILED")
return 1
# ========================================================================
# Summary
# ========================================================================
print("\n" + "=" * 70)
print("All tests PASSED!")
print("=" * 70)
print("\nYour custom GPU operator is working correctly!")
print("You can now use it in your PyTorch models like any built-in op.")
print("\nExample usage:")
print(" x = torch.randn(100, device='cuda')")
print(" y = square(x) # Uses your custom CUDA kernel")
print(" loss = y.sum()")
print(" loss.backward() # Gradients computed automatically")
print("=" * 70 + "\n")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,5 @@
# Custom PyTorch Kernel Sample Requirements
cuda-python>=13.0.0
cuda-core>=0.6.0
torch>=2.0.0

View File

@@ -0,0 +1,81 @@
# Sample: TensorFlow Custom GPU Operator
## Description
Learn how to add a custom GPU operation to TensorFlow using `cuda.core` with `tf.py_function`. This sample implements a custom **ReLU operation** (y = max(0, x)) for rapid prototyping of GPU operations.
## Key Question Answered
**Q: How do I add a custom GPU op to TensorFlow?**
**A:** Use `tf.py_function` to wrap cuda.core kernels:
1. Write CUDA kernels (forward + backward) with grid-stride loops
2. Compile them with cuda.core
3. Wrap in Python functions
4. Use `tf.py_function` to call from TensorFlow
5. Register gradients with `@tf.custom_gradient`
## Requirements
- NVIDIA GPU with Compute Capability 7.0+
- CUDA Toolkit 13.0+
- Python 3.10+
- TensorFlow 2.10+
- cuda-python >= 13.0.0
- cuda-core >= 0.6.0 (required for LEGACY_DEFAULT_STREAM)
- numpy >= 2.3.2
- CuPy (for device pointer access)
## Installation
```bash
cd python/3_FrameworkInterop/customTensorFlowKernel
pip install -r requirements.txt
```
## How to Run
```bash
python customTensorFlowKernel.py
python customTensorFlowKernel.py --size 1000000
```
## Usage Example
```python
import tensorflow as tf
from customTensorFlowKernel import custom_relu
# Simple usage
x = tf.random.normal([100], dtype=tf.float32)
y = custom_relu(x)
# In a Keras model
model = tf.keras.Sequential([
tf.keras.layers.Dense(128),
tf.keras.layers.Lambda(custom_relu),
tf.keras.layers.Dense(10)
])
```
## Key Concepts
- **tf.py_function**: Bridges TensorFlow and Python code using cuda.core (has overhead, not XLA-compatible)
- **@tf.custom_gradient**: Registers custom backward pass
- **cuda.core**: Primary GPU manager (device, stream, kernel compilation)
- **CuPy**: Internal helper for device pointer access only
## Production Alternatives
This sample is for rapid prototyping. For production:
- **TensorFlow C++ Custom Op**: Full performance, XLA compatible
- **XLA Custom Calls**: For XLA-compiled models
- See TensorFlow documentation for details
## See Also
- [cuda.core Documentation](https://nvidia.github.io/cuda-python/cuda-core/latest/)
- [TensorFlow tf.py_function](https://www.tensorflow.org/api_docs/python/tf/py_function)
- [TensorFlow @custom_gradient](https://www.tensorflow.org/api_docs/python/tf/custom_gradient)
- [TensorFlow C++ Custom Op Guide](https://www.tensorflow.org/guide/create_op)
- [CuPy Documentation](https://docs.cupy.dev/)

View File

@@ -0,0 +1,430 @@
# 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
# distribution 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.
"""
TensorFlow Custom GPU Operator using cuda.core
Question: How do I add a custom GPU op to TensorFlow?
Answer: This sample shows rapid prototyping with cuda.core + tf.py_function.
This sample implements a custom ReLU operation (y = max(0, x)) to demonstrate:
- Writing CUDA kernels (forward + backward) with grid-stride loops
- Compiling with cuda.core
- Integrating with TensorFlow via tf.py_function
- Proper gradient registration
Dependencies:
- tensorflow: Deep learning framework
- cuda-core: GPU kernel compilation and launch
(requires >=0.6.0 for LEGACY_DEFAULT_STREAM)
- cuda-python: CUDA driver API bindings
- cupy: Array operations and device pointer access
Note: This approach uses tf.py_function for rapid prototyping. For production
TensorFlow applications, use TensorFlow's C++ Custom Op API.
"""
import sys
try:
# CuPy is required for array operations and device pointer access
import cupy as cp
import tensorflow as tf
from cuda.core import (
LEGACY_DEFAULT_STREAM,
Device,
LaunchConfig,
Program,
ProgramOptions,
launch,
)
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install: pip install tensorflow cupy cuda-python cuda-core")
sys.exit(1)
# ============================================================================
# Step 1: Define CUDA Kernels
# ============================================================================
# Simple element-wise ReLU: y = max(0, x)
RELU_KERNEL = """
extern "C" __global__
void relu_forward_kernel(const float* x, float* y, int n)
{
// Grid-stride loop: each thread processes multiple elements
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int stride = gridDim.x * blockDim.x;
for (int i = idx; i < n; i += stride) {
y[i] = x[i] > 0.0f ? x[i] : 0.0f;
}
}
extern "C" __global__
void relu_backward_kernel(const float* x, const float* grad_y, float* grad_x, int n)
{
// Grid-stride loop: each thread processes multiple elements
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int stride = gridDim.x * blockDim.x;
for (int i = idx; i < n; i += stride) {
grad_x[i] = x[i] > 0.0f ? grad_y[i] : 0.0f;
}
}
"""
# ============================================================================
# Step 2: Kernel Compilation and Caching
# ============================================================================
# Compile kernel once per device and cache it to avoid recompilation overhead
# In real training loops, this avoids paying compilation cost on every forward.
_kernel_cache = {}
def _get_relu_kernels(device):
"""
Get or compile the ReLU kernels for a given device.
Parameters
----------
device : Device
CUDA device object
Returns
-------
tuple
(forward_kernel, backward_kernel) compiled CUDA kernels
"""
# Cache key based on device to avoid recompiling for the same GPU
key = device.pci_bus_id
if key not in _kernel_cache:
# Compile the kernel with appropriate architecture
opts = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
prog = Program(RELU_KERNEL, code_type="c++", options=opts)
mod = prog.compile("cubin")
forward_kernel = mod.get_kernel("relu_forward_kernel")
backward_kernel = mod.get_kernel("relu_backward_kernel")
_kernel_cache[key] = (forward_kernel, backward_kernel)
return _kernel_cache[key]
def _launch_relu_forward(x_np):
"""
Internal function: Launch forward CUDA kernel.
Takes numpy array, returns numpy array.
Uses CuPy for array operations and device pointer access, cuda.core for
device/stream management.
Note: LEGACY_DEFAULT_STREAM doesn't require explicit cleanup, but kernel
launch failures should be handled by the caller. CuPy arrays are
automatically cleaned up when they go out of scope.
"""
device = Device()
# Ensure this device is current (TensorFlow usually does this already)
device.set_current()
# Get compiled kernel (cached)
forward_kernel, _ = _get_relu_kernels(device)
# Convert numpy to CuPy (CPU-to-GPU copy)
# CuPy is used for array operations and getting device pointers
x_cp = cp.asarray(x_np)
y_cp = cp.empty_like(x_cp)
# Configure kernel launch
n = int(x_cp.size)
threads_per_block = 256
blocks_per_grid = (n + threads_per_block - 1) // threads_per_block
config = LaunchConfig(grid=blocks_per_grid, block=threads_per_block)
# Launch on the legacy default stream (stream 0) for TensorFlow interop
launch(
LEGACY_DEFAULT_STREAM, config, forward_kernel, x_cp.data.ptr, y_cp.data.ptr, n
)
# Return as numpy array (GPU-to-CPU copy via cp.asnumpy)
return cp.asnumpy(y_cp)
def _launch_relu_backward(x_np, grad_y_np):
"""
Internal function: Launch backward CUDA kernel.
Takes numpy arrays, returns numpy array.
Uses CuPy for array operations and device pointer access, cuda.core for
device/stream management.
Note: LEGACY_DEFAULT_STREAM doesn't require explicit cleanup, but kernel
launch failures should be handled by the caller. CuPy arrays are
automatically cleaned up when they go out of scope.
"""
device = Device()
# Ensure this device is current (TensorFlow usually does this already)
device.set_current()
# Get compiled kernel (cached)
_, backward_kernel = _get_relu_kernels(device)
# Convert numpy to CuPy (CPU-to-GPU copy)
# CuPy is used for array operations and getting device pointers
x_cp = cp.asarray(x_np)
grad_y_cp = cp.asarray(grad_y_np)
grad_x_cp = cp.empty_like(x_cp)
# Configure kernel launch
n = int(x_cp.size)
threads_per_block = 256
blocks_per_grid = (n + threads_per_block - 1) // threads_per_block
config = LaunchConfig(grid=blocks_per_grid, block=threads_per_block)
# Launch on the legacy default stream (stream 0) for TensorFlow interop
launch(
LEGACY_DEFAULT_STREAM,
config,
backward_kernel,
x_cp.data.ptr,
grad_y_cp.data.ptr,
grad_x_cp.data.ptr,
n,
)
# Return as numpy array (GPU-to-CPU copy via cp.asnumpy)
return cp.asnumpy(grad_x_cp)
# ============================================================================
# Step 3: TensorFlow Integration via tf.py_function
# ============================================================================
@tf.custom_gradient
def custom_relu(x):
"""
Custom ReLU operation using cuda.core.
This function provides a TensorFlow-native interface to custom CUDA kernels
compiled with cuda.core. The implementation uses tf.py_function internally
to bridge TensorFlow and cuda.core.
Parameters
----------
x : tf.Tensor
Input tensor (must be float32 on GPU)
Returns
-------
tf.Tensor
Output tensor with ReLU applied
Examples
--------
>>> x = tf.random.normal([100], dtype=tf.float32)
>>> y = custom_relu(x)
>>> # Use in models
>>> model = tf.keras.Sequential([
... tf.keras.layers.Dense(128),
... tf.keras.layers.Lambda(custom_relu), # Custom ReLU
... tf.keras.layers.Dense(10)
... ])
"""
# Validate input
if x.dtype != tf.float32:
raise ValueError("custom_relu only supports float32 tensors")
# Forward pass using tf.py_function
# py_function allows us to call arbitrary Python code (including cuda.core)
y = tf.py_function(func=_launch_relu_forward, inp=[x], Tout=tf.float32)
# Restore shape information (py_function loses shape)
y.set_shape(x.shape)
# Define gradient function
def grad_fn(grad_y):
"""Backward pass using custom CUDA kernel"""
grad_x = tf.py_function(
func=_launch_relu_backward, inp=[x, grad_y], Tout=tf.float32
)
grad_x.set_shape(x.shape)
return grad_x
return y, grad_fn
# ============================================================================
# Step 4: Testing and Verification
# ============================================================================
def main():
"""Test the custom ReLU operation."""
import argparse
parser = argparse.ArgumentParser(
description="Custom TensorFlow ReLU Operator using cuda.core"
)
parser.add_argument(
"--size", type=int, default=10000, help="Number of elements (default: 10000)"
)
args = parser.parse_args()
# Device info
device = Device()
device.set_current()
major, minor = device.compute_capability
print("\nDevice Information:")
print(f" Name: {device.name}")
print(f" Compute Capability: sm_{major}.{minor}")
print("\n" + "=" * 70)
print("Custom TensorFlow ReLU Operator Test")
print("=" * 70)
# ========================================================================
# Test 1: Forward Pass Correctness
# ========================================================================
print("\n" + "-" * 70)
print("Test 1: Forward Pass")
print("-" * 70)
# Run on the first visible GPU (respects CUDA_VISIBLE_DEVICES),
# aligning with cuda.core Device().
with tf.device("/GPU:0"):
x = tf.random.normal([args.size], dtype=tf.float32)
# Custom ReLU operation
y_custom = custom_relu(x)
# TensorFlow reference
y_reference = tf.nn.relu(x)
# Check correctness
max_error = tf.reduce_max(tf.abs(y_custom - y_reference)).numpy()
print(f"Max absolute error: {max_error:.2e}")
if tf.reduce_all(tf.abs(y_custom - y_reference) < 1e-5):
print("[PASS] Forward pass PASSED")
else:
print("[FAIL] Forward pass FAILED")
return 1
# ========================================================================
# Test 2: Backward Pass (Gradient) Correctness
# ========================================================================
print("\n" + "-" * 70)
print("Test 2: Backward Pass")
print("-" * 70)
with tf.device("/GPU:0"):
x_custom = tf.random.normal([args.size], dtype=tf.float32)
x_reference = tf.identity(x_custom)
# Compute gradients with GradientTape
with tf.GradientTape() as tape_custom:
tape_custom.watch(x_custom)
y_custom = custom_relu(x_custom)
grad_custom = tape_custom.gradient(y_custom, x_custom)
with tf.GradientTape() as tape_reference:
tape_reference.watch(x_reference)
y_reference = tf.nn.relu(x_reference)
grad_reference = tape_reference.gradient(y_reference, x_reference)
# Check gradients
max_grad_error = tf.reduce_max(tf.abs(grad_custom - grad_reference)).numpy()
print(f"Max gradient error: {max_grad_error:.2e}")
if tf.reduce_all(tf.abs(grad_custom - grad_reference) < 1e-5):
print("[PASS] Backward pass PASSED")
else:
print("[FAIL] Backward pass FAILED")
return 1
# ========================================================================
# Test 3: Multi-dimensional Tensors
# ========================================================================
print("\n" + "-" * 70)
print("Test 3: Multi-dimensional Tensors")
print("-" * 70)
with tf.device("/GPU:0"):
# Test with 2D tensor
x_2d = tf.random.normal([100, 100], dtype=tf.float32)
y_2d_custom = custom_relu(x_2d)
y_2d_reference = tf.nn.relu(x_2d)
if tf.reduce_all(tf.abs(y_2d_custom - y_2d_reference) < 1e-5):
print("[PASS] 2D tensor test PASSED")
else:
print("[FAIL] 2D tensor test FAILED")
return 1
# Test with 3D tensor
x_3d = tf.random.normal([10, 20, 30], dtype=tf.float32)
y_3d_custom = custom_relu(x_3d)
y_3d_reference = tf.nn.relu(x_3d)
if tf.reduce_all(tf.abs(y_3d_custom - y_3d_reference) < 1e-5):
print("[PASS] 3D tensor test PASSED")
else:
print("[FAIL] 3D tensor test FAILED")
return 1
# ========================================================================
# Summary
# ========================================================================
print("\n" + "=" * 70)
print("All tests PASSED!")
print("=" * 70)
print("\nYour custom GPU operator is working correctly!")
print("You can now use it in your TensorFlow models.")
print("\nExample usage:")
print(" x = tf.random.normal([100], dtype=tf.float32)")
print(" y = custom_relu(x) # Uses your custom CUDA kernel")
print(" ")
print(" # In a model:")
print(" model = tf.keras.Sequential([")
print(" tf.keras.layers.Dense(128),")
print(" tf.keras.layers.Lambda(custom_relu),")
print(" tf.keras.layers.Dense(10)")
print(" ])")
print("=" * 70 + "\n")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,14 @@
# TensorFlow Custom GPU Operator using cuda.core
#
# This sample demonstrates a cuda.core-first approach:
# - cuda-python and cuda-core: Primary GPU management (device, stream, kernel)
# - TensorFlow 2.10+: Deep learning framework (tf.py_function, tf.custom_gradient)
# - CuPy: Internal helper for device pointer access only
#
# Note: cuda-core>=0.6.0 is required for LEGACY_DEFAULT_STREAM constant
numpy>=2.3.2
tensorflow>=2.10.0
cupy-cuda13x>=13.0.0
cuda-python>=13.0.0
cuda-core>=0.6.0

View File

@@ -0,0 +1,140 @@
# ipcMemoryPool (Python)
## Description
This sample demonstrates how to share GPU memory between Python
processes using CUDA Inter-Process Communication (IPC) and
`cuda.core`'s IPC-enabled memory pools.
By default each process has its own CUDA virtual address space and
cannot see allocations made by another process. With an IPC-enabled
`DeviceMemoryResource` the parent allocates once, and the child
process maps that same physical GPU memory into its own address space
so both read and write the same bytes. The sample performs a
round-trip test:
1. Parent creates an IPC-enabled `DeviceMemoryResource` and allocates
a `Buffer`.
2. Parent fills the buffer with a known pattern.
3. Parent sends the `Buffer` to a child process through an
`multiprocessing.Queue`. cuda.core's pickle reducers re-create the
memory resource and map the buffer in the child.
4. Child verifies the parent's pattern, writes a new pattern, and
signals completion.
5. Parent verifies the child's writes.
## What You'll Learn
- Enabling IPC on a `DeviceMemoryResource` with `ipc_enabled=True`
- Sending `Buffer` objects across process boundaries via `mp.Queue`
- How cuda.core's pickle reducers rebuild the MR and map the buffer
in the receiving process
- Why `multiprocessing` must use the `"spawn"` start method with CUDA
- Detecting IPC support at runtime (POSIX file-descriptor handle
type, memory-pool support, Linux-only)
## Key Libraries
- [`cuda.core`](https://nvidia.github.io/cuda-python/cuda-core/latest/) - IPC-enabled memory resources and buffer reducers
- `cupy` - zero-copy views over the shared device memory via DLPack
- `multiprocessing` - standard library process management
## Key APIs
### From `cuda.core`
- `DeviceMemoryResource(device, options=DeviceMemoryResourceOptions(ipc_enabled=True))` - create an IPC-enabled memory pool
- `DeviceMemoryResourceOptions(max_size=..., ipc_enabled=True)` - configure the underlying pool
- `mr.allocate(nbytes)` - allocate a `Buffer` from the IPC pool
- `Buffer.is_mapped` - True when the buffer is usable in the current process
- `Device.properties.memory_pools_supported` - runtime feature check
- `Device.properties.handle_type_posix_file_descriptor_supported` - runtime feature check
### From `cuda_samples_utils`
- `print_gpu_info()` - print device name and compute capability
## Requirements
### Hardware
- NVIDIA GPU with Compute Capability 7.0 or higher
- Device that supports CUDA memory pools and POSIX file-descriptor IPC handles (the sample detects and reports this at startup)
- Minimum GPU memory: 512 MB
### Software
- Linux x86_64 (POSIX file-descriptor IPC handles are not available on Windows or macOS)
- CUDA Toolkit 13.0 or newer (matches `cuda-python` 13.x)
- Python 3.10 or newer
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## Installation
Install the required packages from `requirements.txt`:
```bash
cd /path/to/cuda-samples/python/4_DistributedComputing/ipcMemoryPool
pip install -r requirements.txt
```
The `requirements.txt` installs:
- `cuda-python` (>=13.0.0)
- `cuda-core` (>=0.6.0)
- `cupy-cuda13x` (>=13.0.0)
## How to Run
### Basic usage
```bash
cd cuda-samples/python/4_DistributedComputing/ipcMemoryPool
python ipcMemoryPool.py
```
### With custom parameters
```bash
# Larger shared buffer
python ipcMemoryPool.py --elements 65536
# Use a specific GPU
python ipcMemoryPool.py --device 1
```
On platforms or devices that do not support CUDA IPC, the sample
prints a diagnostic and exits cleanly with status 0.
## Expected Output
```
Device: <Your GPU Name>
Compute Capability: <X.Y>
Created IPC-enabled DeviceMemoryResource (is_ipc_enabled=True)
Parent wrote pattern (first 5 values): [100. 101. 102. 103. 104.]
Parent sent buffer to child pid=<pid>; waiting...
[child pid=<pid>] received buffer: is_mapped=True, size=4096
Parent sees child's pattern (first 5 values): [-0. -1. -2. -3. -4.]
IPC round-trip: OK
```
**Note:** Device name, compute capability, and child PID will vary
based on your system.
## Files
- `ipcMemoryPool.py` - Python implementation using `cuda.core` IPC memory pools
- `README.md` - This file
- `requirements.txt` - Sample dependencies
- `../../Utilities/cuda_samples_utils.py` - Common utilities (imported by this sample)
## See Also
- [CUDA Python Documentation](https://nvidia.github.io/cuda-python/)
- [`cuda.core` memory API](https://nvidia.github.io/cuda-python/cuda-core/latest/api.html#memory)
- Upstream `cuda.core` IPC tests: [`test_memory_ipc.py`](https://github.com/NVIDIA/cuda-python/blob/main/cuda_core/tests/memory_ipc/test_memory_ipc.py)
- [CUDA IPC programming guide](https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#interprocess-communication)

View File

@@ -0,0 +1,220 @@
# 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.
"""
IPC Memory Pool with cuda.core
Share GPU memory between Python processes using CUDA Inter-Process
Communication (IPC) and cuda.core's IPC-enabled memory pools. By default
each worker process has its own CUDA virtual address space and cannot see
allocations made by another process. With an IPC-enabled
``DeviceMemoryResource`` the parent can allocate once, and the child
process can map that same physical GPU memory into its own address space
so both read and write the same bytes.
The sample does a round-trip test:
1. Parent creates an IPC-enabled ``DeviceMemoryResource`` and allocates
a ``Buffer``.
2. Parent fills the buffer with a known pattern.
3. Parent sends the ``Buffer`` to a child process through an
``mp.Queue`` - cuda.core's pickle reducers take care of re-creating
the memory resource and mapping the buffer in the child.
4. Child verifies the parent's pattern, writes a new pattern, and
signals completion.
5. Parent verifies the child's writes.
IPC requires Linux (POSIX file-descriptor handles) and device support for
memory pools. On unsupported platforms the sample prints a diagnostic and
exits cleanly.
"""
import multiprocessing as mp
import platform
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
try:
import cupy as cp
import numpy as np
from cuda.core import (
Device,
DeviceMemoryResource,
DeviceMemoryResourceOptions,
)
from cuda_samples_utils import print_gpu_info # noqa: E402
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
CHILD_TIMEOUT_SEC = 30
def check_ipc_support(device) -> bool:
"""Return True if this device/platform supports CUDA IPC memory pools."""
if platform.system() != "Linux":
print(
f"IPC via POSIX file descriptors is only supported on Linux "
f"(detected {platform.system()})."
)
return False
if not device.properties.memory_pools_supported:
print("Device does not support CUDA memory pools.")
return False
if not device.properties.handle_type_posix_file_descriptor_supported:
print("Device/platform does not support POSIX-fd IPC handles.")
return False
return True
def child_worker(q_in, q_out, n_elements, parent_seed, child_seed):
"""Runs in a separate process. Verifies and modifies the shared buffer."""
device = Device(0)
device.set_current()
pid = mp.current_process().pid
# The Buffer (and its MR) are reconstructed and mapped in this process
# when the queued object is unpickled. Both ``is_mapped`` flags are
# True here.
buffer = q_in.get(timeout=CHILD_TIMEOUT_SEC)
print(
f"[child pid={pid}] received buffer: is_mapped={buffer.is_mapped}, "
f"size={buffer.size}"
)
# Build a zero-copy CuPy view of the shared device memory.
arr = cp.from_dlpack(buffer).view(dtype=cp.float32)
# Verify the parent's pattern.
expected_parent = cp.arange(n_elements, dtype=cp.float32) + float(parent_seed)
if not cp.allclose(arr, expected_parent):
print("[child] ERROR: parent's pattern did not match expectation")
buffer.close()
q_out.put("fail")
return
# Write a new pattern for the parent to verify.
arr[:] = cp.arange(n_elements, dtype=cp.float32) * float(child_seed)
device.sync()
buffer.close()
q_out.put("done")
def main() -> int:
import argparse
parser = argparse.ArgumentParser(
description="Share a GPU buffer between two processes via CUDA IPC"
)
parser.add_argument(
"--elements",
type=int,
default=1024,
help="Number of float32 elements in the shared buffer (default: 1024)",
)
parser.add_argument("--device", type=int, default=0, help="CUDA device id")
args = parser.parse_args()
# CUDA is incompatible with the ``fork`` start method because forked
# children inherit a corrupt CUDA state. Always use ``spawn``.
mp.set_start_method("spawn", force=True)
device = Device(args.device)
device.set_current()
print_gpu_info(device)
if not check_ipc_support(device):
print("\nCUDA IPC is not available on this system; exiting cleanly.")
return 0
N = args.elements
nbytes = N * np.dtype(np.float32).itemsize
parent_seed = 100
child_seed = -1.0
# Create an IPC-enabled memory pool. Buffers allocated from this MR
# are picklable and can be shared across processes.
mr = DeviceMemoryResource(
device,
options=DeviceMemoryResourceOptions(
max_size=max(nbytes * 4, 1 << 20),
ipc_enabled=True,
),
)
print(
"Created IPC-enabled DeviceMemoryResource "
f"(is_ipc_enabled={mr.is_ipc_enabled})"
)
buffer = mr.allocate(nbytes)
try:
# Fill the buffer with a known pattern from the parent side.
arr = cp.from_dlpack(buffer).view(dtype=cp.float32)
arr[:] = cp.arange(N, dtype=cp.float32) + float(parent_seed)
device.sync()
print(f"Parent wrote pattern (first 5 values): {arr[:5].get()}")
# Launch the child process and hand the buffer over.
q_to_child = mp.Queue()
q_from_child = mp.Queue()
child = mp.Process(
target=child_worker,
args=(q_to_child, q_from_child, N, parent_seed, child_seed),
)
child.start()
q_to_child.put(buffer)
print(f"Parent sent buffer to child pid={child.pid}; waiting...")
msg = q_from_child.get(timeout=CHILD_TIMEOUT_SEC)
child.join(timeout=CHILD_TIMEOUT_SEC)
if msg != "done" or child.exitcode != 0:
print(f"Child failed: msg={msg!r}, exitcode={child.exitcode}")
return 1
# Verify the child's writes are visible from the parent.
device.sync()
got = arr[:5].get()
expected = (np.arange(N, dtype=np.float32) * child_seed)[:5]
print(f"Parent sees child's pattern (first 5 values): {got}")
if np.allclose(got, expected):
print("IPC round-trip: OK")
return 0
print(f"IPC round-trip: FAILED (expected {expected})")
return 1
finally:
buffer.close()
mr.close()
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,4 @@
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0
numpy>=1.24.0

View File

@@ -0,0 +1,110 @@
# Sample: multiGPUGradientAverage (Python)
## Description
This sample demonstrates gradient averaging across multiple GPUs using MPI and cuda.core. Each GPU computes local gradients, which are synchronized (averaged) across all GPUs using MPI Allreduce with host-staging (GPU → CPU → MPI → CPU → GPU) for maximum compatibility.
## What you will learn
- How to initialize MPI for multi-process GPU communication
- How to map MPI ranks to CUDA devices consistently
- How to integrate cuda.core streams with CuPy using `ExternalStream`
- How to compile and launch custom CUDA kernels using cuda.core
- How to use cuda.core Event for GPU timing measurements
- How to use MPI Allreduce with host-staging for universal compatibility
## Prerequisites
- Python 3.10+
- CUDA Toolkit 13.0+
- Standard MPI implementation (OpenMPI, MPICH, or Intel MPI)
- Multiple NVIDIA GPUs (tested with 2+ GPUs)
## Installation
```bash
pip install mpi4py cupy-cuda13x cuda-python cuda-core
```
## Running
**IMPORTANT:** This sample **MUST** be run with `mpirun` with at least 2 processes.
```bash
# Single node (2 GPUs)
mpirun -np 2 python multiGPUGradientAverage.py --size 10000
# Single node (4 GPUs)
mpirun -np 4 python multiGPUGradientAverage.py --size 10000
# With specific GPUs
CUDA_VISIBLE_DEVICES=0,2 mpirun -np 2 python multiGPUGradientAverage.py
```
## Sample Output
```
[Rank 0] World size = 4
======================================================================
Multi-GPU Gradient Average Demo
======================================================================
Number of MPI ranks (GPUs): 4
Gradient vector length per GPU: 10000
Device: NVIDIA GeForce RTX 4090
Computation: gradients computed on GPU via cuda.core.
Communication: gradients averaged via MPI_Allreduce on host (CPU) buffers.
======================================================================
Sample averaged gradient values (rank 0):
avg_grad[0] = 1.500000
avg_grad[5000] = 6.500000
avg_grad[9999] = 11.499000
Expected values:
expected[0] = 1.500000
expected[5000] = 6.500000
expected[9999] = 11.499000
Verifying gradient averaging correctness...
[PASS] Gradient averaging is correct.
[PASS] Gradient averaging is correct on all ranks.
Performance:
Kernel time (GPU only): 0.123 ms
MPI communication time (host-staging, end-to-end): 0.456 ms
Total time: 0.579 ms
======================================================================
Demo complete.
======================================================================
```
## Key Technical Details
The sample uses cuda.core streams and makes CuPy use them via `ExternalStream`:
```python
stream = device.create_stream()
cp.cuda.ExternalStream(int(stream.handle)).use()
```
GPU timing is measured using cuda.core Event:
```python
from cuda.core import EventOptions
timing_options = EventOptions(enable_timing=True)
start_event = stream.record(options=timing_options)
# ... GPU work ...
end_event = stream.record(options=timing_options)
end_event.sync()
kernel_time = end_event - start_event # Returns milliseconds
```
The host-staging pattern transfers data GPU → CPU → MPI → CPU → GPU for universal MPI compatibility without requiring CUDA-aware MPI.
## Troubleshooting
**Error: "This sample requires at least 2 MPI processes!"**
Solution: Run with `mpirun -np 2 python multiGPUGradientAverage.py`

View File

@@ -0,0 +1,416 @@
# 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
# distribution 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.
"""
Multi-GPU Gradient Average using MPI and cuda.core (Host-staging Allreduce)
Question: How do I synchronize gradients across GPUs?
Answer:
Each GPU (each MPI rank) computes local gradients on device via CUDA.
Gradients are then averaged across ranks via an MPI Allreduce over host
(CPU) buffers, following the classic data-parallel training pattern.
This sample shows how to:
- Initialize MPI for multi-process GPU workloads
- Map MPI ranks to GPUs
- Use cuda.core for kernel compilation and execution
- Integrate cuda.core with CuPy using the stream protocol
- Perform gradient averaging with MPI Allreduce (using host staging)
- Use cuda.core Event for GPU timing measurements
- Verify correctness of distributed gradient synchronization
Key concepts: Allreduce, NCCL collectives (conceptually), distributed training
Note:
- All gradient computation and validation happen on GPUs.
- MPI Allreduce is executed on CPU (host) buffers via a simple
GPU -> CPU -> MPI -> CPU -> GPU staging pattern so that the sample
works on any MPI stack, without requiring CUDA-aware MPI.
- In production deep learning frameworks (e.g., PyTorch DDP), NCCL
usually implements the GPU Allreduce directly; the communication
pattern and semantics are the same as demonstrated here.
"""
import sys
from pathlib import Path
# Add parent directory to path to import utilities
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
from cuda_samples_utils import verify_array_result # noqa: E402
try:
import cupy as cp
from cuda.core import (
Device,
EventOptions,
LaunchConfig,
Program,
ProgramOptions,
launch,
system,
)
from mpi4py import MPI
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install: pip install mpi4py cupy-cuda12x cuda-python cuda-core")
sys.exit(1)
# ============================================================================
# CUDA device selection and stream management
# ============================================================================
def init_device(rank: int):
"""
Initialize CUDA device and stream for this MPI rank.
For a simple single-node run, we map rank % num_gpus to a device id.
This covers both the common case (world_size == num_gpus) and the case
where multiple ranks share a GPU.
Returns
-------
tuple[Device, Stream]
CUDA device object and stream object.
"""
num_gpus = system.get_num_devices()
if num_gpus == 0:
raise RuntimeError("No CUDA devices available")
dev_id = rank % num_gpus # simple mapping: rank -> GPU in round-robin
try:
device = Device(dev_id)
except (RuntimeError, ValueError) as e:
if rank == 0:
print(f"Warning: Cannot assign GPU {dev_id}, using GPU 0. Error: {e}")
device = Device(0)
device.set_current()
# Align CuPy with cuda.core's chosen device ID
cp.cuda.Device(device.device_id).use()
# Create cuda.core stream and make CuPy use it
stream = device.create_stream()
cp.cuda.ExternalStream(int(stream.handle)).use()
return device, stream
# ============================================================================
# CUDA kernel definition and compilation
# ============================================================================
# Tiny CUDA kernel to initialize local "gradients"
# Uses grid-stride loop to handle arrays larger than grid size
INIT_KERNEL = r"""
extern "C" __global__
void init_grad_kernel(float* grad, int n, int rank)
{
// Grid-stride loop: each thread processes multiple elements
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t stride = gridDim.x * blockDim.x;
for (size_t i = tid; i < n; i += stride) {
// Gradient value depends on MPI rank so we can verify reduction:
// grad_i = rank + 0.001 * i
grad[i] = rank + 0.001f * i;
}
}
"""
_kernel_cache = {}
def get_init_kernel(device: Device):
"""Compile (or retrieve cached) init_grad_kernel for this device."""
key = device.pci_bus_id
if key not in _kernel_cache:
opts = ProgramOptions(std="c++17", arch=f"sm_{device.arch}")
prog = Program(INIT_KERNEL, code_type="c++", options=opts)
mod = prog.compile("cubin")
_kernel_cache[key] = mod.get_kernel("init_grad_kernel")
return _kernel_cache[key]
# ============================================================================
# Local gradient computation on each GPU
# ============================================================================
def compute_local_gradients(
num_elements: int, device: Device, stream: object, rank: int
) -> cp.ndarray:
"""
Compute a local "gradient" vector on the current GPU.
For demo purposes, we initialize:
grad[i] = rank + 0.001 * i
Parameters
----------
num_elements : int
Length of gradient vector.
device : Device
CUDA device object.
stream : Stream
CUDA stream object (created at device initialization).
rank : int
MPI rank ID.
Returns
-------
cupy.ndarray
Gradient vector on GPU.
"""
# Create gradient array (CuPy uses the stream set at device initialization)
grad = cp.empty(num_elements, dtype=cp.float32)
# Use a CUDA kernel compiled with cuda.core to fill the array
kernel = get_init_kernel(device)
threads_per_block = 256
blocks_per_grid = (num_elements + threads_per_block - 1) // threads_per_block
config = LaunchConfig(grid=blocks_per_grid, block=threads_per_block)
# Launch kernel using cuda.core stream
launch(stream, config, kernel, grad.data.ptr, num_elements, rank)
return grad
# ============================================================================
# MPI Allreduce to average gradients (host-staging)
# ============================================================================
def average_gradients(
local_grad: cp.ndarray, comm: object, world_size: int
) -> cp.ndarray:
"""
Average gradients across all MPI ranks using host-staging Allreduce.
Steps:
1. Copy local gradients from GPU to CPU (NumPy).
2. Perform MPI_Allreduce on host buffers.
3. Divide by world_size to obtain the average.
4. Copy the averaged gradients back to GPU.
This pattern is environment-agnostic and works on any MPI stack.
"""
assert local_grad.dtype == cp.float32
# GPU -> CPU
local_host = local_grad.get() # NumPy array on host
avg_host = local_host.copy()
# Allreduce on host buffers
comm.Allreduce(local_host, avg_host, op=MPI.SUM)
# Average
avg_host /= world_size
# CPU -> GPU
avg_grad = cp.asarray(avg_host)
return avg_grad
# ============================================================================
# Testing and verification
# ============================================================================
def main():
"""Demo: Multi-GPU gradient averaging with MPI (host-staging Allreduce)."""
import argparse
# Initialize MPI
comm = MPI.COMM_WORLD
world_size = comm.Get_size()
rank = comm.Get_rank()
parser = argparse.ArgumentParser(
description=(
"Multi-GPU Gradient Average with mpi4py + cuda.core "
"(host-staging Allreduce)"
)
)
parser.add_argument(
"--size",
type=int,
default=1024,
help="Number of gradient elements per GPU (default: 1024)",
)
args = parser.parse_args()
num_elements = args.size
# Initialize device and stream
device = None
stream = None
try:
device, stream = init_device(rank)
if rank == 0:
print(f"[Rank 0] World size = {world_size}")
comm.Barrier()
# Validate world size
if world_size < 2:
if rank == 0:
print("=" * 70)
print("ERROR: This sample requires at least 2 MPI processes!")
print("=" * 70)
print("\nPlease run with mpirun:")
print(" mpirun -np 2 python multiGPUGradientAverage.py")
print(" mpirun -np 4 python multiGPUGradientAverage.py --size 10000")
print("\nFor multi-GPU systems:")
print(" mpirun -np N python multiGPUGradientAverage.py")
print(" (where N = number of GPUs)")
print("=" * 70)
sys.exit(1)
# Validate input
if num_elements <= 0:
if rank == 0:
print("Error: --size must be positive")
sys.exit(1)
if rank == 0:
print("\n" + "=" * 70)
print("Multi-GPU Gradient Average Demo")
print("=" * 70)
print(f"Number of MPI ranks (GPUs): {world_size}")
print(f"Gradient vector length per GPU: {num_elements}")
print(f"Device: {device.name}")
print("Computation: gradients computed on GPU via cuda.core.")
print(
"Communication: gradients averaged via MPI_Allreduce on host "
"(CPU) buffers."
)
print("=" * 70)
# Step 1: Compute local gradients on each GPU
# Use cuda.core Event for GPU timing measurements
timing_options = EventOptions(enable_timing=True)
start_event = stream.record(options=timing_options)
local_grad = compute_local_gradients(num_elements, device, stream, rank)
# Record end event and synchronize to ensure timing is complete
end_event = stream.record(options=timing_options)
end_event.sync()
# Calculate elapsed time: Event subtraction returns milliseconds
kernel_time = end_event - start_event
# Step 2: Average gradients across all ranks (host-staging Allreduce)
# Use CPU timing for MPI communication (host-staging includes GPU↔CPU transfers)
import time
comm_start = time.time()
avg_grad = average_gradients(local_grad, comm, world_size)
comm_time = (time.time() - comm_start) * 1000 # Convert to ms
# Step 3: Sanity check on rank 0
# For each element i:
# local_grad_r[i] = r + 0.001 * i, r = 0..world_size-1
# Sum over ranks:
# sum[i] = sum_r r + 0.001 * i * world_size
# Average:
# avg[i] = (0 + ... + (world_size-1))/world_size + 0.001 * i
# = (world_size - 1)/2 + 0.001 * i
#
# We verify this formula.
expected_base = (world_size - 1) / 2.0
i0 = 0
i1 = num_elements // 2
i2 = num_elements - 1
# Copy a few sample elements back to host for printing on rank 0
if rank == 0:
avg_host_samples = avg_grad[[i0, i1, i2]].get()
print("\nSample averaged gradient values (rank 0):")
print(f" avg_grad[{i0}] = {avg_host_samples[0]:.6f}")
print(f" avg_grad[{i1}] = {avg_host_samples[1]:.6f}")
print(f" avg_grad[{i2}] = {avg_host_samples[2]:.6f}")
expected0 = expected_base + 0.001 * i0
expected1 = expected_base + 0.001 * i1
expected2 = expected_base + 0.001 * i2
print("\nExpected values:")
print(f" expected[{i0}] = {expected0:.6f}")
print(f" expected[{i1}] = {expected1:.6f}")
print(f" expected[{i2}] = {expected2:.6f}")
# All ranks perform a full-array correctness check on GPU
expected_full = expected_base + 0.001 * cp.arange(
num_elements, dtype=cp.float32
)
# Use utility function to verify results
if rank == 0:
print("\nVerifying gradient averaging correctness...")
ok = verify_array_result(
avg_grad, expected_full, rtol=1e-5, atol=1e-8, verbose=(rank == 0)
)
# Ensure all ranks agree on correctness
ok_all = comm.allreduce(ok, op=MPI.LAND)
if rank == 0:
if ok_all:
print("[PASS] Gradient averaging is correct on all ranks.")
else:
print(
"[FAIL] Gradient averaging mismatch detected on one or more ranks."
)
print("\nPerformance:")
print(f" Kernel time (GPU only): {kernel_time:.3f} ms")
print(
" MPI communication time (host-staging, end-to-end): "
f"{comm_time:.3f} ms"
)
print(f" Total time: {kernel_time + comm_time:.3f} ms")
print("\n" + "=" * 70)
print("Demo complete.")
print("=" * 70)
return 0 if ok_all else 1
finally:
# Clean up stream resources
if stream is not None:
stream.close()
cp.cuda.Stream.null.use() # Reset CuPy's current stream to the null stream
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,21 @@
# Multi-GPU Gradient Average Sample Requirements
# MPI Python bindings for distributed communication
mpi4py>=3.1.4
# GPU array library (NumPy-compatible arrays on CUDA)
# Use cupy-cuda11x, cupy-cuda12x, or cupy-cuda13x depending on your CUDA version
cupy-cuda13x>=13.0.0
# CUDA Python bindings (low-level CUDA driver API)
cuda-python>=13.0.0
# cuda.core - Modern Python interface for CUDA
# Provides Program, LaunchConfig, Device, and launch APIs
cuda-core>=0.6.0
# Note: This sample uses host-staging for MPI communication
# Standard MPI installation is sufficient (no CUDA-aware MPI required)
# Install MPI using system package manager:
# Ubuntu/Debian: sudo apt-get install openmpi-bin libopenmpi-dev
# Or build from source: https://www.open-mpi.org/software/ompi/

View File

@@ -0,0 +1,190 @@
# Sample: simpleP2P (Python)
## Description
This sample demonstrates peer-to-peer (P2P) memory access between multiple GPUs in CUDA using the cuda.core Python library. P2P allows GPUs to directly access each other's memory without routing data through the host (CPU), enabling efficient multi-GPU applications. This sample detects P2P-capable GPUs, enables peer access, measures bandwidth using CUDA events for accurate GPU-side timing, and launches kernels (using grid-stride loops) that read from one GPU's memory and write to another GPU's memory.
## What you will learn
- How to detect multiple CUDA-capable GPUs using `system.get_num_devices()` and `Device(id)`
- How to check P2P capability between GPU pairs using `device.can_access_peer()`
- How to enable and disable peer access using `DeviceMemoryResource.peer_accessible_by`
- How to allocate device memory on specific GPUs using `DeviceMemoryResource`
- How to perform direct GPU-to-GPU memory transfers with explicit event-based synchronization
- How to measure P2P bandwidth using CUDA events for accurate GPU-side timing
- How to use event-based synchronization between streams for sequential bandwidth measurement
- How to launch kernels on one GPU that access memory from another GPU
- How to compile and launch CUDA kernels using cuda.core's `Program` and `launch` APIs with grid-stride loops
- How to validate multi-GPU computation results
- How to properly clean up resources using try/finally blocks
## Key libraries
- `numpy` - CPU array operations and data initialization
- `cuda-core` - Modern Python interface to CUDA runtime with full P2P support
## Key APIs
**From cuda.core:**
- `system` – Pre-instantiated singleton for system-level CUDA information
- `system.get_num_devices()` – Get number of CUDA-capable devices
- `Device(id)` – Get specific CUDA device handle
- `device.can_access_peer(peer)` – Check if this device can access peer device memory
- `device.set_current()` – Set active device for subsequent operations
- `device.create_stream()` – Create CUDA stream for kernel execution
- `DeviceMemoryResource(device)` – Create memory resource for specific GPU
- `memory_resource.peer_accessible_by` – Get/set which devices can access this memory pool's allocations
- Example: `mr.peer_accessible_by = [1]` grants device 1 access
- Example: `mr.peer_accessible_by = []` revokes all access
- `PinnedMemoryResource()` – Allocate pinned (page-locked) host memory
- `EventOptions(enable_timing=True)` – Create options for CUDA events with timing enabled
- `stream.record(options=event_options)` – Record a CUDA event on a stream
- `event.elapsed_time(start_event)` – Get elapsed time in milliseconds between two events
- `stream.wait_event(event)` – Make a stream wait for an event to complete
- `stream.close()` – Clean up stream resources
- `Program()` – Compile CUDA C++ kernel code
- `LaunchConfig()` – Configure kernel launch parameters (grid, block)
- `launch()` – Launch compiled kernel with arguments
- `buffer.copy_from(src, stream=stream)` – Copy data from source buffer asynchronously
- `buffer.copy_to(dst, stream=stream)` – Copy data to destination buffer asynchronously
**From DLPack:**
- `numpy.from_dlpack()` – Create NumPy array view of memory buffer
**Memory Management:**
- Resources (streams, buffers) should be cleaned up using try/finally blocks to ensure proper cleanup even if errors occur
- Streams should be explicitly closed with `stream.close()` in finally blocks
## Peer-to-Peer (P2P): When to Use
### Benefits
- **Direct GPU-to-GPU transfers**: Bypass host memory for faster communication
- **Higher bandwidth**: PCIe or NVLink bandwidth between GPUs (up to 600 GB/s with NVLink)
- **Lower latency**: No CPU involvement in data transfers
- **Efficient multi-GPU**: Essential for scaling deep learning, HPC, and simulation workloads
- **Simplified programming**: Kernels can directly access remote GPU memory
### Requirements
- **Two or more GPUs**: System must have multiple CUDA-capable GPUs
- **P2P support**: GPUs must be P2P-capable (check with `can_access_peer()`)
- **PCIe topology**: Usually requires GPUs on same PCIe root complex
- **Platform support**: Not available on Mac OSX, limited on ARM platforms
### Best Use Cases
1. Multi-GPU deep learning training (model parallelism, data parallelism)
2. Large-scale scientific simulations across multiple GPUs
3. Real-time rendering with multiple GPUs
4. GPU clusters with direct GPU communication
5. Reducing CPU-GPU traffic in multi-GPU systems
## Requirements
1. **Two or more NVIDIA Graphics Cards** with CUDA support and P2P capability
2. **CUDA Drivers** installed on your system
3. **CUDA Toolkit 13.0+** installed on your system
4. **Python 3.10 or newer**
5. **Proper PCIe topology** (GPUs should be on same PCIe root complex for best performance)
**Note**: This sample will gracefully exit if fewer than 2 GPUs are detected or if P2P is not supported between any GPU pair.
**Install packages:**
```bash
pip install -r requirements.txt
```
Or manually:
```bash
pip install numpy>=2.3.2 cuda-core>=0.6.0 cuda-python>=13.0.0
```
## How to run
Basic usage:
```bash
# Run with default parameters (16M elements = 64MB)
python simpleP2P.py
```
With custom parameters:
```bash
# Use 32M elements (128MB)
python simpleP2P.py --num_elements 33554432
# Show help
python simpleP2P.py --help
```
### Command line arguments
- `--num_elements`: Number of elements in arrays (default: 16777216)
- Each array uses `num_elements * 4 bytes` (float32)
- Default: 64 MB per array
- Sample allocates 2 device buffers + 1 host buffer
## Expected Output
```
======================================================================
simpleP2P - CUDA Python Sample
======================================================================
Starting...
Checking for multiple GPUs...
CUDA-capable device count: 2
Checking GPU(s) for support of peer to peer memory access...
> Peer access from Tesla T10 (GPU0) -> Tesla T10 (GPU1): Yes
> Peer access from Tesla T10 (GPU1) -> Tesla T10 (GPU0): Yes
Using GPU0 (Tesla T10) and GPU1 (Tesla T10)
Allocating buffers (64MB on GPU0, GPU1 and CPU Host)...
Peer access enabled: GPU0 <-> GPU1
Peer access status: MR0 accessible by (1,), MR1 accessible by (0,)
Memory allocated successfully
Measuring P2P bandwidth...
Performing 100 ping-pong copies between GPUs...
P2P bandwidth: 12.37 GB/s
Preparing host buffer and memcpy to GPU0...
Data initialized and copied to GPU
Compiling CUDA kernel...
Kernels compiled successfully
Run kernel on GPU1, taking source data from GPU0 and writing to GPU1...
Kernel execution complete
Run kernel on GPU0, taking source data from GPU1 and writing to GPU0...
Kernel execution complete
Copy data back to host from GPU0 and verify results...
Checking results...
Comparing 16,777,216 elements...
Test PASSED
[PASS] Validation PASSED
Disabling peer access...
Peer access revoked: MR0 accessible by (), MR1 accessible by ()
======================================================================
simpleP2P completed successfully!
======================================================================
Shutting down...
```
**Note**: P2P bandwidth varies based on:
- PCIe generation
- NVLink
- System topology and configuration
## Files
- `simpleP2P.py` – Main Python implementation
- `README.md` – This file
- `requirements.txt` – Python package dependencies

View File

@@ -0,0 +1,6 @@
# simpleP2P - Requirements
# Install with: pip install -r requirements.txt
numpy>=2.3.2
cuda-python>=13.0.0
cuda-core>=0.6.0

View File

@@ -0,0 +1,377 @@
# 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
# distribution 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.
import argparse
import sys
from pathlib import Path
try:
import numpy as np
from cuda.core import (
Device,
DeviceMemoryResource,
EventOptions,
LaunchConfig,
PinnedMemoryResource,
Program,
ProgramOptions,
launch,
system,
)
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
sys.exit(1)
# Add parent directory to path to import utilities
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Utilities"))
from cuda_samples_utils import verify_array_result # noqa: E402
# CUDA kernel for simple P2P operation
SIMPLE_P2P_KERNEL = """
extern "C" __global__
void SimpleKernel(float *src, float *dst, int N) {
// Grid-stride loop pattern for canonical CUDA kernel
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t stride = gridDim.x * blockDim.x;
for (size_t i = tid; i < N; i += stride) {
dst[i] = src[i] * 2.0f;
}
}
"""
def run(num_elements=1024 * 1024 * 16):
"""
Demonstrates peer-to-peer (P2P) memory access between multiple GPUs using cuda.core.
This function shows how to:
1. Detect and verify multiple GPUs with P2P capability
2. Enable peer access between GPUs
3. Perform direct GPU-to-GPU memory transfers
4. Launch kernels that access memory from other GPUs
5. Measure P2P bandwidth
6. Validate results
Parameters
----------
num_elements : int
Number of elements in arrays (default: 16M elements = 64MB)
"""
print("\n" + "=" * 70)
print("simpleP2P - CUDA Python Sample")
print("=" * 70)
print("\nStarting...")
# Check for multiple GPUs
print("\nChecking for multiple GPUs...")
num_devices = system.get_num_devices()
print(f"CUDA-capable device count: {num_devices}")
if num_devices < 2:
print(
"Two or more GPUs with Peer-to-Peer access capability are "
"required, waiving this sample."
)
return 2
# Get device properties
devices = [Device(i) for i in range(num_devices)]
# Check for P2P capability
print("\nChecking GPU(s) for support of peer to peer memory access...")
p2p_capable_gpus = [-1, -1]
for i in range(num_devices):
p2p_capable_gpus[0] = i
for j in range(num_devices):
if i == j:
continue
# Check peer access capability using cuda.core
i_access_j = devices[i].can_access_peer(devices[j])
j_access_i = devices[j].can_access_peer(devices[i])
print(
f"> Peer access from {devices[i].name} (GPU{i}) -> "
f"{devices[j].name} (GPU{j}): {'Yes' if i_access_j else 'No'}"
)
print(
f"> Peer access from {devices[j].name} (GPU{j}) -> "
f"{devices[i].name} (GPU{i}): {'Yes' if j_access_i else 'No'}"
)
if i_access_j and j_access_i:
p2p_capable_gpus[1] = j
break
if p2p_capable_gpus[1] != -1:
break
if p2p_capable_gpus[0] == -1 or p2p_capable_gpus[1] == -1:
print("\nTwo or more GPUs with Peer-to-Peer access capability are required.")
print(
"Peer to Peer access is not available amongst GPUs in the system, "
"waiving test."
)
return 2
# Use first pair of P2P capable GPUs detected
gpuid = [p2p_capable_gpus[0], p2p_capable_gpus[1]]
dev0 = devices[gpuid[0]]
dev1 = devices[gpuid[1]]
print(f"\nUsing GPU{gpuid[0]} ({dev0.name}) and GPU{gpuid[1]} ({dev1.name})")
# Allocate buffers with P2P access
buf_size = num_elements * np.dtype(np.float32).itemsize
print(
f"\nAllocating buffers ({int(buf_size / 1024 / 1024)}MB on "
f"GPU{gpuid[0]}, GPU{gpuid[1]} and CPU Host)..."
)
# Allocate on GPU 0 and grant access to GPU 1
dev0.set_current()
mr0 = DeviceMemoryResource(dev0)
mr0.peer_accessible_by = [gpuid[1]] # Grant GPU 1 access to GPU 0's memory
g0 = mr0.allocate(buf_size)
# Allocate on GPU 1 and grant access to GPU 0
dev1.set_current()
mr1 = DeviceMemoryResource(dev1)
mr1.peer_accessible_by = [gpuid[0]] # Grant GPU 0 access to GPU 1's memory
g1 = mr1.allocate(buf_size)
print(f" Peer access enabled: GPU{gpuid[0]} <-> GPU{gpuid[1]}")
print(
f" Peer access status: MR0 accessible by {mr0.peer_accessible_by}, "
f"MR1 accessible by {mr1.peer_accessible_by}"
)
# Allocate pinned host memory
pinned_mr = PinnedMemoryResource()
h0 = pinned_mr.allocate(buf_size)
print(" Memory allocated successfully")
# Create streams
stream0 = dev0.create_stream()
stream1 = dev1.create_stream()
try:
# P2P bandwidth test using CUDA events for accurate GPU-side timing
print("\nMeasuring P2P bandwidth...")
print(" Performing 100 ping-pong copies between GPUs...")
event_options = EventOptions(enable_timing=True)
sync_event0 = None
sync_event1 = None
# Record start event on stream0
start_event = stream0.record(options=event_options)
for i in range(100):
# Ping-pong copy between GPUs with explicit event-based synchronization
if i % 2 == 0:
# Wait for previous stream1 copy to complete (if any)
if sync_event1 is not None:
stream0.wait(sync_event1)
# Copy g0 -> g1 on stream0
g1.copy_from(g0, stream=stream0)
# Record event on stream0 to signal completion of this copy
sync_event0 = stream0.record(options=EventOptions(enable_timing=False))
else:
# Wait for previous stream0 copy to complete
if sync_event0 is not None:
stream1.wait(sync_event0)
# Copy g1 -> g0 on stream1
g0.copy_from(g1, stream=stream1)
# Record event on stream1 to signal completion of this copy
sync_event1 = stream1.record(options=EventOptions(enable_timing=False))
# Wait for last stream1 copy to complete
if sync_event1 is not None:
stream0.wait(sync_event1)
# Record end event on stream0 after all copies have been enqueued
end_event = stream0.record(options=event_options)
end_event.sync()
# Elapsed time in milliseconds (using subtraction operator)
time_memcpy = end_event - start_event
bandwidth = (1.0 / (time_memcpy / 1000.0)) * (100.0 * buf_size) / (1024.0**3)
print(f" P2P bandwidth: {bandwidth:.2f} GB/s")
# Prepare host buffer and initialize data
print(f"\nPreparing host buffer and memcpy to GPU{gpuid[0]}...")
# Create numpy view and initialize
h0_array = np.from_dlpack(h0).view(dtype=np.float32)
h0_array[:] = (np.arange(num_elements, dtype=np.float32) % 4096).astype(
np.float32
)
# Copy to GPU 0
dev0.set_current()
g0.copy_from(h0, stream=stream0)
stream0.sync()
print(" Data initialized and copied to GPU")
# Compile kernel for both GPUs
print("\nCompiling CUDA kernel...")
dev0.set_current()
program_options = ProgramOptions(std="c++17", arch=f"sm_{dev0.arch}")
prog = Program(SIMPLE_P2P_KERNEL, code_type="c++", options=program_options)
mod0 = prog.compile("cubin")
kernel0 = mod0.get_kernel("SimpleKernel")
dev1.set_current()
program_options = ProgramOptions(std="c++17", arch=f"sm_{dev1.arch}")
prog = Program(SIMPLE_P2P_KERNEL, code_type="c++", options=program_options)
mod1 = prog.compile("cubin")
kernel1 = mod1.get_kernel("SimpleKernel")
print(" Kernels compiled successfully")
# Launch configuration
threads = 512
blocks = (num_elements + threads - 1) // threads
config = LaunchConfig(grid=blocks, block=threads)
# Run kernel on GPU 1, reading from GPU 0, writing to GPU 1
print(
f"\nRun kernel on GPU{gpuid[1]}, taking source data from "
f"GPU{gpuid[0]} and writing to GPU{gpuid[1]}..."
)
dev1.set_current()
launch(stream1, config, kernel1, g0, g1, np.int32(num_elements))
stream1.sync()
print(" Kernel execution complete")
# Run kernel on GPU 0, reading from GPU 1, writing to GPU 0
print(
f"\nRun kernel on GPU{gpuid[0]}, taking source data from "
f"GPU{gpuid[1]} and writing to GPU{gpuid[0]}..."
)
dev0.set_current()
launch(stream0, config, kernel0, g1, g0, np.int32(num_elements))
stream0.sync()
print(" Kernel execution complete")
# Copy data back to host and verify
print(f"\nCopy data back to host from GPU{gpuid[0]} and verify results...")
g0.copy_to(h0, stream=stream0)
stream0.sync()
# Verify results
print("\nChecking results...")
print(f" Comparing {num_elements:,} elements...")
# Input data goes through two kernels, each multiplying by 2.0.
expected = (np.arange(num_elements, dtype=np.float32) % 4096) * 4.0
# Use utility function for verification (handles both numpy and cupy arrays)
if verify_array_result(h0_array, expected, rtol=1e-5, atol=1e-6, verbose=True):
print(" [PASS] Validation PASSED")
success = True
else:
print(" [FAIL] Validation FAILED")
# Show first few errors for debugging
errors = np.where(~np.isclose(h0_array, expected, rtol=1e-5, atol=1e-6))[0]
print(f" Number of mismatches: {len(errors)}")
for idx in errors[:10]:
print(
f" Error @ element {idx}: got {h0_array[idx]}, "
f"expected {expected[idx]}"
)
success = False
# Disable peer access
print("\nDisabling peer access...")
mr0.peer_accessible_by = [] # Revoke GPU 1's access to GPU 0's memory
mr1.peer_accessible_by = [] # Revoke GPU 0's access to GPU 1's memory
print(
f" Peer access revoked: MR0 accessible by {mr0.peer_accessible_by}, "
f"MR1 accessible by {mr1.peer_accessible_by}"
)
print("\n" + "=" * 70)
if success:
print("simpleP2P completed successfully!")
else:
print("simpleP2P FAILED!")
print("=" * 70 + "\n")
return 0 if success else 1
finally:
# Cleanup streams and buffers
print("Shutting down...")
stream0.close()
stream1.close()
def main():
"""Main entry point with argument parsing."""
parser = argparse.ArgumentParser(
description=(
"Demonstrate peer-to-peer (P2P) memory access between "
"multiple GPUs with CUDA"
)
)
parser.add_argument(
"--num_elements",
type=int,
default=1024 * 1024 * 16, # 16M elements = 64MB
help="Number of elements in arrays (default: 16777216 = 64MB)",
)
args = parser.parse_args()
# Validate arguments
if args.num_elements <= 0:
print("Error: num_elements must be positive")
return 1
try:
exit_code = run(num_elements=args.num_elements)
sys.exit(exit_code)
except Exception as e:
print(f"\nError: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

134
python/Utilities/README.md Normal file
View File

@@ -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` (>=0.6.0)
- `cupy-cuda13x` (>=13.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.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 `../1_GettingStarted/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.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

View File

@@ -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",
]

View File

@@ -0,0 +1,144 @@
# 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
# distribution 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.
"""
Common CUDA utilities for Python samples.
This module provides common utility functions for CUDA samples including:
- Package requirements checking
- Result verification
- GPU device information
Requirements:
- Python 3.10+
- CUDA Toolkit 13.0+ (recommended; matches cuda-python 13.x)
- cuda-python >= 13.0.0
- cuda-core >= 0.6.0
- cupy-cuda13x >= 13.0.0
- numpy >= 2.3.2 (when used with samples that install it)
"""
def check_cuda_requirements() -> bool:
"""
Check if required CUDA packages are available.
Returns
-------
bool
True if requirements are met, False otherwise
"""
try:
import cupy as cp # noqa: F401
from cuda.core import Device # noqa: F401
return True
except ImportError as e:
print(f"Error: Required package not found: {e}")
print("Please install from requirements.txt:")
print(" pip install -r requirements.txt")
return False
def verify_array_result(
result, expected, rtol: float = 1e-5, atol: float = 1e-8, verbose: bool = True
) -> bool:
"""
Verify that computed result matches expected result.
Automatically detects whether arrays are NumPy or CuPy and uses the
appropriate library without unnecessary data transfers.
Parameters
----------
result : numpy.ndarray or cupy.ndarray
Computed result array.
expected : numpy.ndarray or cupy.ndarray
Expected result array.
rtol : float
Relative tolerance (default: 1e-5)
atol : float
Absolute tolerance (default: 1e-8)
verbose : bool
Whether to print verification result (default: True).
Returns
-------
bool
True if results match, False otherwise.
Raises
------
TypeError
If arrays are not both NumPy or both CuPy, or if CuPy is needed
but not available.
"""
import numpy as np
is_np = isinstance(result, np.ndarray) and isinstance(expected, np.ndarray)
if is_np:
allclose = np.allclose
abs_ = np.abs
max_ = np.max
else:
import cupy as cp
is_cp = isinstance(result, cp.ndarray) and isinstance(expected, cp.ndarray)
if not is_cp:
raise TypeError(
"verify_array_result expects both arrays to be either "
"numpy.ndarray or cupy.ndarray"
)
allclose = cp.allclose
abs_ = cp.abs
max_ = cp.max
if allclose(result, expected, rtol=rtol, atol=atol):
if verbose:
print("Test PASSED")
return True
else:
max_error = max_(abs_(result - expected))
if verbose:
print(f"Test FAILED - Max error: {max_error}")
return False
def print_gpu_info(device) -> None:
"""
Print GPU device information.
Parameters
----------
device : cuda.core.Device
CUDA device object
"""
print(f"Device: {device.name}")
cc = device.compute_capability
print(f"Compute Capability: {cc.major}.{cc.minor}")

7
python/requirements.txt Normal file
View File

@@ -0,0 +1,7 @@
# CUDA Python Samples — common base for CUDA 13.x
# Install sample-specific extras from each sample's requirements.txt (RAPIDS, TensorFlow, etc.).
cuda-python>=13.0.0
cuda-core>=0.6.0
cupy-cuda13x>=13.0.0
numpy>=2.3.2