mirror of
https://github.com/NVIDIA/cuda-samples.git
synced 2026-10-13 00:08:26 +08:00
CUDA 13.4 samples update - v13.4-public
Release 13.4 of the CUDA Samples supported by CUDA Toolkit 13.4. See Changelog for more information.
This commit is contained in:
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Linux",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"${workspaceFolder}/../../../Common"
|
||||
],
|
||||
"defines": [],
|
||||
"compilerPath": "/usr/local/cuda/bin/nvcc",
|
||||
"cStandard": "gnu17",
|
||||
"cppStandard": "gnu++14",
|
||||
"intelliSenseMode": "linux-gcc-x64",
|
||||
"configurationProvider": "ms-vscode.makefile-tools"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"nvidia.nsight-vscode-edition",
|
||||
"ms-vscode.cpptools",
|
||||
"ms-vscode.makefile-tools"
|
||||
]
|
||||
}
|
||||
@@ -1,34 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/Modules")
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/DetectCudaArch.cmake")
|
||||
|
||||
if(SAMPLE_SKIP_BUILD)
|
||||
return()
|
||||
endif()
|
||||
|
||||
project(simpleCudaGraphs LANGUAGES C CXX CUDA)
|
||||
|
||||
include("${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/CudaSampleCommon.cmake")
|
||||
|
||||
find_package(CUDAToolkit REQUIRED)
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
add_executable(simpleCudaGraphs_explicit simpleCudaGraphs_explicit.cu)
|
||||
add_executable(simpleCudaGraphs_capture simpleCudaGraphs_capture.cu)
|
||||
|
||||
set(CMAKE_CUDA_ARCHITECTURES 75 80 86 87 89 90 100 110 120)
|
||||
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Wno-deprecated-gpu-targets")
|
||||
if(ENABLE_CUDA_DEBUG)
|
||||
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -G") # enable cuda-gdb (may significantly affect performance on some targets)
|
||||
else()
|
||||
set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -lineinfo") # add line information to all builds for debug tools (exclusive to -G option)
|
||||
endif()
|
||||
foreach(target simpleCudaGraphs_explicit simpleCudaGraphs_capture)
|
||||
target_compile_options(${target} PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:--extended-lambda>)
|
||||
target_compile_features(${target} PRIVATE cxx_std_17 cuda_std_17)
|
||||
set_target_properties(${target} PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
|
||||
target_include_directories(${target} PRIVATE ${CUDAToolkit_INCLUDE_DIRS}/cccl)
|
||||
endforeach()
|
||||
|
||||
# Include directories and libraries
|
||||
include_directories(../../../Common)
|
||||
|
||||
# Source file
|
||||
# Add target for simpleCudaGraphs
|
||||
add_executable(simpleCudaGraphs simpleCudaGraphs.cu)
|
||||
|
||||
target_compile_options(simpleCudaGraphs PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:--extended-lambda>)
|
||||
|
||||
target_compile_features(simpleCudaGraphs PRIVATE cxx_std_17 cuda_std_17)
|
||||
|
||||
set_target_properties(simpleCudaGraphs PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
|
||||
|
||||
# Include installation configuration
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/../../../cmake/InstallSamples.cmake)
|
||||
include(InstallSamples)
|
||||
setup_samples_install()
|
||||
|
||||
@@ -1,30 +1,126 @@
|
||||
# simpleCudaGraphs - Simple CUDA Graphs
|
||||
# Sample: CUDA Graphs (simpleCudaGraphs)
|
||||
|
||||
## Description
|
||||
|
||||
A demonstration of CUDA Graphs creation, instantiation and launch using Graphs APIs and Stream Capture APIs.
|
||||
Demonstrates how to create, instantiate, and launch CUDA Graphs using two different approaches: building the graph explicitly, node by node, with `cudaGraphAddNode`, and building it automatically by recording an existing stream sequence with `cudaStreamBeginCapture` / `cudaStreamEndCapture`. A CUDA Graph records a sequence of GPU operations — memory copies, kernel launches, and host callbacks — as a reusable DAG (Directed Acyclic Graph). Once instantiated, the graph can be submitted to the GPU with a single API call per launch, reducing per-launch CPU overhead compared to issuing each operation individually.
|
||||
|
||||
The workload is a two-pass parallel reduction: 16M `float` values are reduced to per-block partial sums (`reduce` kernel), then to a single `double` result (`reduceFinal` kernel). Both samples use `cub::BlockReduce` for the in-kernel reduction. Each graph is launched several times, and the host input is refilled with new random values before every launch, to show that one instantiated graph can reprocess different data each iteration.
|
||||
|
||||
This sample is split into two standalone executables plus a shared header:
|
||||
|
||||
| File | Role |
|
||||
|---|---|
|
||||
| `simpleCudaGraphs_explicit.cu` | Explicit construction via the unified `cudaGraphAddNode` API |
|
||||
| `simpleCudaGraphs_capture.cu` | Automatic construction via stream capture |
|
||||
| `simpleCudaGraphs.cuh` | Shared reduction kernels, host callback (and its data type), and input-fill helper used by both |
|
||||
|
||||
## What You'll Learn
|
||||
|
||||
- Building a CUDA Graph node by node using the unified `cudaGraphAddNode` API with a typed `cudaGraphNodeParams`
|
||||
- Expressing dependencies between nodes so the runtime enforces the correct execution order
|
||||
- Building the same graph automatically by recording stream operations between `cudaStreamBeginCapture` and `cudaStreamEndCapture`
|
||||
- Instantiating a graph with `cudaGraphInstantiate` (one-time compilation cost) and launching it repeatedly with `cudaGraphLaunch`
|
||||
- Cloning a graph with `cudaGraphClone` to produce independent executable instances
|
||||
- Adding memcpy, kernel, and host-callback nodes through the single polymorphic `cudaGraphAddNode` entry point
|
||||
- Reusing one instantiated graph across many launches by refilling its input buffer between launches (and synchronizing so the graph's H2D copy consumes the data before the host overwrites it)
|
||||
- Using `cub::BlockReduce` for efficient block-level reductions
|
||||
|
||||
## Key Concepts
|
||||
|
||||
CUDA Graphs, Stream Capture
|
||||
- **CUDA Graph** — a DAG of GPU operations captured once and replayed many times; each replay is a single `cudaGraphLaunch` call regardless of graph size
|
||||
- **Graph Node** — an individual operation in the graph: memcpy, kernel launch, or host callback
|
||||
- **Node dependency** — an edge from node A to node B means B cannot start until A completes; expressed as a dependency list passed to `cudaGraphAddNode`
|
||||
- **Instantiation** — `cudaGraphInstantiate` compiles the graph into an optimized executable form (`cudaGraphExec_t`); this is the one-time setup cost; subsequent launches reuse it
|
||||
- **Stream Capture** — `cudaStreamBeginCapture` / `cudaStreamEndCapture` records stream operations into a graph automatically; the runtime infers the same node structure as the manually built graph
|
||||
- **Graph Clone** — `cudaGraphClone` deep-copies the graph structure; each clone can be independently instantiated and launched, useful when multiple CPU threads need to launch the same graph concurrently
|
||||
- **Graph reuse** — an instantiated graph is a template you launch repeatedly; because this graph begins with an H2D copy from a host buffer, refilling that buffer before each launch feeds new data through the same graph (with a `cudaStreamSynchronize` between launches so the copy reads the data before the host overwrites it)
|
||||
- **`cub::BlockReduce`** — CUB's block-scoped reduction primitive; all threads in a block contribute their partial sum and thread 0 receives the block total
|
||||
|
||||
## Supported SM Architectures
|
||||
## Key APIs
|
||||
|
||||
## Supported OSes
|
||||
### CUDA Runtime — Explicit Graph Construction
|
||||
- `cudaGraphCreate` — create an empty graph
|
||||
- `cudaGraphAddNode` — add a node of any type (memcpy, kernel, host, …) via a `cudaGraphNodeParams` struct
|
||||
- `cudaGraphNodeParams` — unified node descriptor: a `.type` tag plus a union of per-type parameters
|
||||
- `cudaGraphGetNodes` — query the number of nodes in a graph
|
||||
|
||||
Linux, Windows
|
||||
### CUDA Runtime — Stream Capture
|
||||
- `cudaStreamBeginCapture` — put a stream into capture mode; subsequent operations are recorded, not executed
|
||||
- `cudaStreamEndCapture` — stop recording and return the captured graph
|
||||
- `cudaLaunchHostFunc` — schedule a CPU callback on a stream (captured as a host node)
|
||||
|
||||
## Supported CPU Architecture
|
||||
### CUDA Runtime — Instantiation and Launch
|
||||
- `cudaGraphInstantiate` — compile a graph into an executable `cudaGraphExec_t`
|
||||
- `cudaGraphLaunch` — submit the entire graph to a stream in a single call
|
||||
- `cudaGraphClone` — deep-copy a graph structure
|
||||
- `cudaStreamSynchronize` — block until the stream's queued work (including the graph's H2D copy) completes, so the host input buffer can be safely refilled for the next launch
|
||||
- `cudaGraphExecDestroy` — release an executable graph
|
||||
- `cudaGraphDestroy` — release a graph
|
||||
|
||||
x86_64, armv7l
|
||||
### CUB
|
||||
- `cub::BlockReduce<T, BLOCK_THREADS>::Sum` — reduce all per-thread values to a single block sum; result lands on thread 0
|
||||
|
||||
## CUDA APIs involved
|
||||
## Requirements
|
||||
|
||||
### [CUDA Runtime API](http://docs.nvidia.com/cuda/cuda-runtime-api/index.html)
|
||||
cudaGraphClone, cudaExtent, cudaGraphLaunch, cudaStreamCreate, cudaLaunchHostFunc, cudaGraphAddMemcpyNode, cudaMallocHost, cudaPitchedPtr, cudaStreamEndCapture, cudaGraphCreate, cudaFreeHost, cudaGraphGetNodes, cudaMemsetAsync, cudaMemcpyAsync, cudaGraphAddHostNode, cudaGraphInstantiate, cudaStreamDestroy, cudaStreamBeginCapture, cudaStreamWaitEvent, cudaEventCreate, cudaMalloc, cudaFree, cudaPos, cudaGraphAddKernelNode, cudaGraphDestroy, cudaEventRecord, cudaGraphsManual, cudaStreamSynchronize, cudaGraphAddMemsetNode, cudaGraphsUsingStreamCapture, cudaGraphExecDestroy
|
||||
### Hardware
|
||||
- NVIDIA GPU with Compute Capability 7.5 or higher
|
||||
|
||||
## Prerequisites
|
||||
### Software
|
||||
- CMake 3.20 or newer
|
||||
- A C++17-capable host compiler
|
||||
|
||||
Download and install the [CUDA Toolkit](https://developer.nvidia.com/cuda-downloads) for your corresponding platform.
|
||||
## How to Build
|
||||
|
||||
## References (for more details)
|
||||
See the [top-level README](../../../README.md#building-cuda-samples) for full build instructions, including how to build all samples or a single sample standalone.
|
||||
|
||||
## How to Run
|
||||
|
||||
**Explicit graph construction:**
|
||||
```bash
|
||||
./simpleCudaGraphs_explicit
|
||||
```
|
||||
|
||||
**Stream capture:**
|
||||
```bash
|
||||
./simpleCudaGraphs_capture
|
||||
```
|
||||
|
||||
## Expected Output
|
||||
|
||||
Both executables build the same 5-node graph (`Graph node count: 5`), confirming that stream capture produces the same structure as the explicit construction. Because the host input is refilled with new random values before every launch, **each of the six launches prints a different reduced sum** — showing that one instantiated graph reprocesses fresh data each iteration. The exact values depend on the platform's `rand()` implementation, but they are reproducible run-to-run and both executables print the same sequence.
|
||||
|
||||
**`simpleCudaGraphs_explicit`**
|
||||
```text
|
||||
GPU Device 0: compute capability X.Y, <smCount> SMs
|
||||
|
||||
Reducing 16777216 elements
|
||||
Threads per block : 512
|
||||
Graph launch iterations: 3
|
||||
|
||||
=== Explicit Graph Construction ===
|
||||
Graph node count: 5
|
||||
[cudaGraphsManual] Host callback final reduced sum = 0.996214
|
||||
[cudaGraphsManual] Host callback final reduced sum = 0.996187
|
||||
[cudaGraphsManual] Host callback final reduced sum = 0.996120
|
||||
|
||||
Cloned graph:
|
||||
[cudaGraphsManual] Host callback final reduced sum = 0.996150
|
||||
[cudaGraphsManual] Host callback final reduced sum = 0.996184
|
||||
[cudaGraphsManual] Host callback final reduced sum = 0.996056
|
||||
```
|
||||
|
||||
**`simpleCudaGraphs_capture`** prints the same six values, each line labeled `[cudaGraphsUsingStreamCapture]` and under a `=== Stream Capture ===` header.
|
||||
|
||||
## Files
|
||||
|
||||
- `simpleCudaGraphs.cuh` — shared code: `THREADS_PER_BLOCK`, the `callBackData_t` type, the `myHostNodeCallback` host callback, the `init_input` host helper, and the two reduction kernels (`reduce`, `reduceFinal`)
|
||||
- `simpleCudaGraphs_explicit.cu` — explicit graph construction via the unified `cudaGraphAddNode` API
|
||||
- `simpleCudaGraphs_capture.cu` — graph construction via stream capture
|
||||
- `CMakeLists.txt` — build configuration
|
||||
- `README.md` — this file
|
||||
|
||||
## See Also
|
||||
|
||||
- [CUDA Programming Guide — CUDA Graphs](https://docs.nvidia.com/cuda/cuda-programming-guide/04-special-topics/cuda-graphs.html#cuda-graphs)
|
||||
- [Technical Blog — Getting Started with CUDA Graphs](https://developer.nvidia.com/blog/cuda-graphs/)
|
||||
- [CUDA Core Compute Libraries — What is CUB?](https://nvidia.github.io/cccl/unstable/cub/index.html#what-is-cub)
|
||||
|
||||
@@ -1,407 +0,0 @@
|
||||
/* Copyright (c) 2022, 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.
|
||||
*/
|
||||
|
||||
#include <cooperative_groups.h>
|
||||
#include <cuda_runtime.h>
|
||||
#include <helper_cuda.h>
|
||||
#include <vector>
|
||||
|
||||
namespace cg = cooperative_groups;
|
||||
|
||||
#define THREADS_PER_BLOCK 512
|
||||
#define GRAPH_LAUNCH_ITERATIONS 3
|
||||
|
||||
typedef struct callBackData
|
||||
{
|
||||
const char *fn_name;
|
||||
double *data;
|
||||
} callBackData_t;
|
||||
|
||||
__global__ void reduce(float *inputVec, double *outputVec, size_t inputSize, size_t outputSize)
|
||||
{
|
||||
__shared__ double tmp[THREADS_PER_BLOCK];
|
||||
|
||||
cg::thread_block cta = cg::this_thread_block();
|
||||
size_t globaltid = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
|
||||
double temp_sum = 0.0;
|
||||
for (int i = globaltid; i < inputSize; i += gridDim.x * blockDim.x) {
|
||||
temp_sum += (double)inputVec[i];
|
||||
}
|
||||
tmp[cta.thread_rank()] = temp_sum;
|
||||
|
||||
cg::sync(cta);
|
||||
|
||||
cg::thread_block_tile<32> tile32 = cg::tiled_partition<32>(cta);
|
||||
|
||||
double beta = temp_sum;
|
||||
double temp;
|
||||
|
||||
for (int i = tile32.size() / 2; i > 0; i >>= 1) {
|
||||
if (tile32.thread_rank() < i) {
|
||||
temp = tmp[cta.thread_rank() + i];
|
||||
beta += temp;
|
||||
tmp[cta.thread_rank()] = beta;
|
||||
}
|
||||
cg::sync(tile32);
|
||||
}
|
||||
cg::sync(cta);
|
||||
|
||||
if (cta.thread_rank() == 0 && blockIdx.x < outputSize) {
|
||||
beta = 0.0;
|
||||
for (int i = 0; i < cta.size(); i += tile32.size()) {
|
||||
beta += tmp[i];
|
||||
}
|
||||
outputVec[blockIdx.x] = beta;
|
||||
}
|
||||
}
|
||||
|
||||
__global__ void reduceFinal(double *inputVec, double *result, size_t inputSize)
|
||||
{
|
||||
__shared__ double tmp[THREADS_PER_BLOCK];
|
||||
|
||||
cg::thread_block cta = cg::this_thread_block();
|
||||
size_t globaltid = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
|
||||
double temp_sum = 0.0;
|
||||
for (int i = globaltid; i < inputSize; i += gridDim.x * blockDim.x) {
|
||||
temp_sum += (double)inputVec[i];
|
||||
}
|
||||
tmp[cta.thread_rank()] = temp_sum;
|
||||
|
||||
cg::sync(cta);
|
||||
|
||||
cg::thread_block_tile<32> tile32 = cg::tiled_partition<32>(cta);
|
||||
|
||||
// do reduction in shared mem
|
||||
if ((blockDim.x >= 512) && (cta.thread_rank() < 256)) {
|
||||
tmp[cta.thread_rank()] = temp_sum = temp_sum + tmp[cta.thread_rank() + 256];
|
||||
}
|
||||
|
||||
cg::sync(cta);
|
||||
|
||||
if ((blockDim.x >= 256) && (cta.thread_rank() < 128)) {
|
||||
tmp[cta.thread_rank()] = temp_sum = temp_sum + tmp[cta.thread_rank() + 128];
|
||||
}
|
||||
|
||||
cg::sync(cta);
|
||||
|
||||
if ((blockDim.x >= 128) && (cta.thread_rank() < 64)) {
|
||||
tmp[cta.thread_rank()] = temp_sum = temp_sum + tmp[cta.thread_rank() + 64];
|
||||
}
|
||||
|
||||
cg::sync(cta);
|
||||
|
||||
if (cta.thread_rank() < 32) {
|
||||
// Fetch final intermediate sum from 2nd warp
|
||||
if (blockDim.x >= 64)
|
||||
temp_sum += tmp[cta.thread_rank() + 32];
|
||||
// Reduce final warp using shuffle
|
||||
for (int offset = tile32.size() / 2; offset > 0; offset /= 2) {
|
||||
temp_sum += tile32.shfl_down(temp_sum, offset);
|
||||
}
|
||||
}
|
||||
// write result for this block to global mem
|
||||
if (cta.thread_rank() == 0)
|
||||
result[0] = temp_sum;
|
||||
}
|
||||
|
||||
void init_input(float *a, size_t size)
|
||||
{
|
||||
for (size_t i = 0; i < size; i++)
|
||||
a[i] = (rand() & 0xFF) / (float)RAND_MAX;
|
||||
}
|
||||
|
||||
void CUDART_CB myHostNodeCallback(void *data)
|
||||
{
|
||||
// Check status of GPU after stream operations are done
|
||||
callBackData_t *tmp = (callBackData_t *)(data);
|
||||
// checkCudaErrors(tmp->status);
|
||||
|
||||
double *result = (double *)(tmp->data);
|
||||
char *function = (char *)(tmp->fn_name);
|
||||
printf("[%s] Host callback final reduced sum = %lf\n", function, *result);
|
||||
*result = 0.0; // reset the result
|
||||
}
|
||||
|
||||
void cudaGraphsManual(float *inputVec_h,
|
||||
float *inputVec_d,
|
||||
double *outputVec_d,
|
||||
double *result_d,
|
||||
size_t inputSize,
|
||||
size_t numOfBlocks)
|
||||
{
|
||||
cudaStream_t streamForGraph;
|
||||
cudaGraph_t graph;
|
||||
std::vector<cudaGraphNode_t> nodeDependencies;
|
||||
cudaGraphNode_t memcpyNode, kernelNode, memsetNode;
|
||||
double result_h = 0.0;
|
||||
|
||||
checkCudaErrors(cudaStreamCreate(&streamForGraph));
|
||||
|
||||
cudaKernelNodeParams kernelNodeParams = {0};
|
||||
cudaMemcpy3DParms memcpyParams = {0};
|
||||
cudaMemsetParams memsetParams = {0};
|
||||
|
||||
memcpyParams.srcArray = NULL;
|
||||
memcpyParams.srcPos = make_cudaPos(0, 0, 0);
|
||||
memcpyParams.srcPtr = make_cudaPitchedPtr(inputVec_h, sizeof(float) * inputSize, inputSize, 1);
|
||||
memcpyParams.dstArray = NULL;
|
||||
memcpyParams.dstPos = make_cudaPos(0, 0, 0);
|
||||
memcpyParams.dstPtr = make_cudaPitchedPtr(inputVec_d, sizeof(float) * inputSize, inputSize, 1);
|
||||
memcpyParams.extent = make_cudaExtent(sizeof(float) * inputSize, 1, 1);
|
||||
memcpyParams.kind = cudaMemcpyHostToDevice;
|
||||
|
||||
memsetParams.dst = (void *)outputVec_d;
|
||||
memsetParams.value = 0;
|
||||
memsetParams.pitch = 0;
|
||||
memsetParams.elementSize = sizeof(float); // elementSize can be max 4 bytes
|
||||
memsetParams.width = numOfBlocks * 2;
|
||||
memsetParams.height = 1;
|
||||
|
||||
checkCudaErrors(cudaGraphCreate(&graph, 0));
|
||||
checkCudaErrors(cudaGraphAddMemcpyNode(&memcpyNode, graph, NULL, 0, &memcpyParams));
|
||||
checkCudaErrors(cudaGraphAddMemsetNode(&memsetNode, graph, NULL, 0, &memsetParams));
|
||||
|
||||
nodeDependencies.push_back(memsetNode);
|
||||
nodeDependencies.push_back(memcpyNode);
|
||||
|
||||
void *kernelArgs[4] = {(void *)&inputVec_d, (void *)&outputVec_d, &inputSize, &numOfBlocks};
|
||||
|
||||
kernelNodeParams.func = (void *)reduce;
|
||||
kernelNodeParams.gridDim = dim3(numOfBlocks, 1, 1);
|
||||
kernelNodeParams.blockDim = dim3(THREADS_PER_BLOCK, 1, 1);
|
||||
kernelNodeParams.sharedMemBytes = 0;
|
||||
kernelNodeParams.kernelParams = (void **)kernelArgs;
|
||||
kernelNodeParams.extra = NULL;
|
||||
|
||||
checkCudaErrors(cudaGraphAddKernelNode(
|
||||
&kernelNode, graph, nodeDependencies.data(), nodeDependencies.size(), &kernelNodeParams));
|
||||
|
||||
nodeDependencies.clear();
|
||||
nodeDependencies.push_back(kernelNode);
|
||||
|
||||
memset(&memsetParams, 0, sizeof(memsetParams));
|
||||
memsetParams.dst = result_d;
|
||||
memsetParams.value = 0;
|
||||
memsetParams.elementSize = sizeof(float);
|
||||
memsetParams.width = 2;
|
||||
memsetParams.height = 1;
|
||||
checkCudaErrors(cudaGraphAddMemsetNode(&memsetNode, graph, NULL, 0, &memsetParams));
|
||||
|
||||
nodeDependencies.push_back(memsetNode);
|
||||
|
||||
memset(&kernelNodeParams, 0, sizeof(kernelNodeParams));
|
||||
kernelNodeParams.func = (void *)reduceFinal;
|
||||
kernelNodeParams.gridDim = dim3(1, 1, 1);
|
||||
kernelNodeParams.blockDim = dim3(THREADS_PER_BLOCK, 1, 1);
|
||||
kernelNodeParams.sharedMemBytes = 0;
|
||||
void *kernelArgs2[3] = {(void *)&outputVec_d, (void *)&result_d, &numOfBlocks};
|
||||
kernelNodeParams.kernelParams = kernelArgs2;
|
||||
kernelNodeParams.extra = NULL;
|
||||
|
||||
checkCudaErrors(cudaGraphAddKernelNode(
|
||||
&kernelNode, graph, nodeDependencies.data(), nodeDependencies.size(), &kernelNodeParams));
|
||||
nodeDependencies.clear();
|
||||
nodeDependencies.push_back(kernelNode);
|
||||
|
||||
memset(&memcpyParams, 0, sizeof(memcpyParams));
|
||||
|
||||
memcpyParams.srcArray = NULL;
|
||||
memcpyParams.srcPos = make_cudaPos(0, 0, 0);
|
||||
memcpyParams.srcPtr = make_cudaPitchedPtr(result_d, sizeof(double), 1, 1);
|
||||
memcpyParams.dstArray = NULL;
|
||||
memcpyParams.dstPos = make_cudaPos(0, 0, 0);
|
||||
memcpyParams.dstPtr = make_cudaPitchedPtr(&result_h, sizeof(double), 1, 1);
|
||||
memcpyParams.extent = make_cudaExtent(sizeof(double), 1, 1);
|
||||
memcpyParams.kind = cudaMemcpyDeviceToHost;
|
||||
checkCudaErrors(
|
||||
cudaGraphAddMemcpyNode(&memcpyNode, graph, nodeDependencies.data(), nodeDependencies.size(), &memcpyParams));
|
||||
nodeDependencies.clear();
|
||||
nodeDependencies.push_back(memcpyNode);
|
||||
|
||||
cudaGraphNode_t hostNode;
|
||||
cudaHostNodeParams hostParams = {0};
|
||||
hostParams.fn = myHostNodeCallback;
|
||||
callBackData_t hostFnData;
|
||||
hostFnData.data = &result_h;
|
||||
hostFnData.fn_name = "cudaGraphsManual";
|
||||
hostParams.userData = &hostFnData;
|
||||
|
||||
checkCudaErrors(
|
||||
cudaGraphAddHostNode(&hostNode, graph, nodeDependencies.data(), nodeDependencies.size(), &hostParams));
|
||||
|
||||
cudaGraphNode_t *nodes = NULL;
|
||||
size_t numNodes = 0;
|
||||
checkCudaErrors(cudaGraphGetNodes(graph, nodes, &numNodes));
|
||||
printf("\nNum of nodes in the graph created manually = %zu\n", numNodes);
|
||||
|
||||
cudaGraphExec_t graphExec;
|
||||
checkCudaErrors(cudaGraphInstantiate(&graphExec, graph, NULL, NULL, 0));
|
||||
|
||||
cudaGraph_t clonedGraph;
|
||||
cudaGraphExec_t clonedGraphExec;
|
||||
checkCudaErrors(cudaGraphClone(&clonedGraph, graph));
|
||||
checkCudaErrors(cudaGraphInstantiate(&clonedGraphExec, clonedGraph, NULL, NULL, 0));
|
||||
|
||||
for (int i = 0; i < GRAPH_LAUNCH_ITERATIONS; i++) {
|
||||
checkCudaErrors(cudaGraphLaunch(graphExec, streamForGraph));
|
||||
}
|
||||
|
||||
checkCudaErrors(cudaStreamSynchronize(streamForGraph));
|
||||
|
||||
printf("Cloned Graph Output.. \n");
|
||||
for (int i = 0; i < GRAPH_LAUNCH_ITERATIONS; i++) {
|
||||
checkCudaErrors(cudaGraphLaunch(clonedGraphExec, streamForGraph));
|
||||
}
|
||||
checkCudaErrors(cudaStreamSynchronize(streamForGraph));
|
||||
|
||||
checkCudaErrors(cudaGraphExecDestroy(graphExec));
|
||||
checkCudaErrors(cudaGraphExecDestroy(clonedGraphExec));
|
||||
checkCudaErrors(cudaGraphDestroy(graph));
|
||||
checkCudaErrors(cudaGraphDestroy(clonedGraph));
|
||||
checkCudaErrors(cudaStreamDestroy(streamForGraph));
|
||||
}
|
||||
|
||||
void cudaGraphsUsingStreamCapture(float *inputVec_h,
|
||||
float *inputVec_d,
|
||||
double *outputVec_d,
|
||||
double *result_d,
|
||||
size_t inputSize,
|
||||
size_t numOfBlocks)
|
||||
{
|
||||
cudaStream_t stream1, stream2, stream3, streamForGraph;
|
||||
cudaEvent_t forkStreamEvent, memsetEvent1, memsetEvent2;
|
||||
cudaGraph_t graph;
|
||||
double result_h = 0.0;
|
||||
|
||||
checkCudaErrors(cudaStreamCreate(&stream1));
|
||||
checkCudaErrors(cudaStreamCreate(&stream2));
|
||||
checkCudaErrors(cudaStreamCreate(&stream3));
|
||||
checkCudaErrors(cudaStreamCreate(&streamForGraph));
|
||||
|
||||
checkCudaErrors(cudaEventCreate(&forkStreamEvent));
|
||||
checkCudaErrors(cudaEventCreate(&memsetEvent1));
|
||||
checkCudaErrors(cudaEventCreate(&memsetEvent2));
|
||||
|
||||
checkCudaErrors(cudaStreamBeginCapture(stream1, cudaStreamCaptureModeGlobal));
|
||||
|
||||
checkCudaErrors(cudaEventRecord(forkStreamEvent, stream1));
|
||||
checkCudaErrors(cudaStreamWaitEvent(stream2, forkStreamEvent, 0));
|
||||
checkCudaErrors(cudaStreamWaitEvent(stream3, forkStreamEvent, 0));
|
||||
|
||||
checkCudaErrors(cudaMemcpyAsync(inputVec_d, inputVec_h, sizeof(float) * inputSize, cudaMemcpyDefault, stream1));
|
||||
|
||||
checkCudaErrors(cudaMemsetAsync(outputVec_d, 0, sizeof(double) * numOfBlocks, stream2));
|
||||
|
||||
checkCudaErrors(cudaEventRecord(memsetEvent1, stream2));
|
||||
|
||||
checkCudaErrors(cudaMemsetAsync(result_d, 0, sizeof(double), stream3));
|
||||
checkCudaErrors(cudaEventRecord(memsetEvent2, stream3));
|
||||
|
||||
checkCudaErrors(cudaStreamWaitEvent(stream1, memsetEvent1, 0));
|
||||
|
||||
reduce<<<numOfBlocks, THREADS_PER_BLOCK, 0, stream1>>>(inputVec_d, outputVec_d, inputSize, numOfBlocks);
|
||||
|
||||
checkCudaErrors(cudaStreamWaitEvent(stream1, memsetEvent2, 0));
|
||||
|
||||
reduceFinal<<<1, THREADS_PER_BLOCK, 0, stream1>>>(outputVec_d, result_d, numOfBlocks);
|
||||
checkCudaErrors(cudaMemcpyAsync(&result_h, result_d, sizeof(double), cudaMemcpyDefault, stream1));
|
||||
|
||||
callBackData_t hostFnData = {0};
|
||||
hostFnData.data = &result_h;
|
||||
hostFnData.fn_name = "cudaGraphsUsingStreamCapture";
|
||||
cudaHostFn_t fn = myHostNodeCallback;
|
||||
checkCudaErrors(cudaLaunchHostFunc(stream1, fn, &hostFnData));
|
||||
checkCudaErrors(cudaStreamEndCapture(stream1, &graph));
|
||||
|
||||
cudaGraphNode_t *nodes = NULL;
|
||||
size_t numNodes = 0;
|
||||
checkCudaErrors(cudaGraphGetNodes(graph, nodes, &numNodes));
|
||||
printf("\nNum of nodes in the graph created using stream capture API = %zu\n", numNodes);
|
||||
|
||||
cudaGraphExec_t graphExec;
|
||||
checkCudaErrors(cudaGraphInstantiate(&graphExec, graph, NULL, NULL, 0));
|
||||
|
||||
cudaGraph_t clonedGraph;
|
||||
cudaGraphExec_t clonedGraphExec;
|
||||
checkCudaErrors(cudaGraphClone(&clonedGraph, graph));
|
||||
checkCudaErrors(cudaGraphInstantiate(&clonedGraphExec, clonedGraph, NULL, NULL, 0));
|
||||
|
||||
for (int i = 0; i < GRAPH_LAUNCH_ITERATIONS; i++) {
|
||||
checkCudaErrors(cudaGraphLaunch(graphExec, streamForGraph));
|
||||
}
|
||||
|
||||
checkCudaErrors(cudaStreamSynchronize(streamForGraph));
|
||||
|
||||
printf("Cloned Graph Output.. \n");
|
||||
for (int i = 0; i < GRAPH_LAUNCH_ITERATIONS; i++) {
|
||||
checkCudaErrors(cudaGraphLaunch(clonedGraphExec, streamForGraph));
|
||||
}
|
||||
|
||||
checkCudaErrors(cudaStreamSynchronize(streamForGraph));
|
||||
|
||||
checkCudaErrors(cudaGraphExecDestroy(graphExec));
|
||||
checkCudaErrors(cudaGraphExecDestroy(clonedGraphExec));
|
||||
checkCudaErrors(cudaGraphDestroy(graph));
|
||||
checkCudaErrors(cudaGraphDestroy(clonedGraph));
|
||||
checkCudaErrors(cudaStreamDestroy(stream1));
|
||||
checkCudaErrors(cudaStreamDestroy(stream2));
|
||||
checkCudaErrors(cudaStreamDestroy(streamForGraph));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
size_t size = 1 << 24; // number of elements to reduce
|
||||
size_t maxBlocks = 512;
|
||||
|
||||
// This will pick the best possible CUDA capable device
|
||||
int devID = findCudaDevice(argc, (const char **)argv);
|
||||
|
||||
printf("%zu elements\n", size);
|
||||
printf("threads per block = %d\n", THREADS_PER_BLOCK);
|
||||
printf("Graph Launch iterations = %d\n", GRAPH_LAUNCH_ITERATIONS);
|
||||
|
||||
float *inputVec_d = NULL, *inputVec_h = NULL;
|
||||
double *outputVec_d = NULL, *result_d;
|
||||
|
||||
checkCudaErrors(cudaMallocHost(&inputVec_h, sizeof(float) * size));
|
||||
checkCudaErrors(cudaMalloc(&inputVec_d, sizeof(float) * size));
|
||||
checkCudaErrors(cudaMalloc(&outputVec_d, sizeof(double) * maxBlocks));
|
||||
checkCudaErrors(cudaMalloc(&result_d, sizeof(double)));
|
||||
|
||||
init_input(inputVec_h, size);
|
||||
|
||||
cudaGraphsManual(inputVec_h, inputVec_d, outputVec_d, result_d, size, maxBlocks);
|
||||
cudaGraphsUsingStreamCapture(inputVec_h, inputVec_d, outputVec_d, result_d, size, maxBlocks);
|
||||
|
||||
checkCudaErrors(cudaFree(inputVec_d));
|
||||
checkCudaErrors(cudaFree(outputVec_d));
|
||||
checkCudaErrors(cudaFree(result_d));
|
||||
checkCudaErrors(cudaFreeHost(inputVec_h));
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
106
cpp/3_CUDA_Features/simpleCudaGraphs/simpleCudaGraphs.cuh
Normal file
106
cpp/3_CUDA_Features/simpleCudaGraphs/simpleCudaGraphs.cuh
Normal file
@@ -0,0 +1,106 @@
|
||||
/* Copyright (c) 2026, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Shared code for the simpleCudaGraphs sample. Both the explicit-graph and
|
||||
* stream-capture demos build a graph around the same two-pass reduction, so the
|
||||
* reduction kernels, the host callback and its data type, and the input-fill
|
||||
* helper live here to avoid duplicating them across the two .cu files.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cub/cub.cuh>
|
||||
#include <cuda_runtime.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#define THREADS_PER_BLOCK 512
|
||||
|
||||
// Payload passed to the host-callback node: the demo name and the reduced result.
|
||||
typedef struct callBackData
|
||||
{
|
||||
const char *fn_name;
|
||||
double *data;
|
||||
} callBackData_t;
|
||||
|
||||
// Host-callback node body: prints the reduced result, then resets it for the next launch.
|
||||
inline void CUDART_CB myHostNodeCallback(void *data)
|
||||
{
|
||||
callBackData_t *tmp = (callBackData_t *)(data);
|
||||
double *result = (double *)(tmp->data);
|
||||
printf("[%s] Host callback final reduced sum = %lf\n", tmp->fn_name, *result);
|
||||
*result = 0.0; // reset the result
|
||||
}
|
||||
|
||||
// Fill the host input buffer with fresh pseudo-random values. Called before each
|
||||
// graph launch so the same graph processes different data every iteration.
|
||||
inline void init_input(float *inputVec, size_t size)
|
||||
{
|
||||
for (size_t i = 0; i < size; i++)
|
||||
inputVec[i] = (rand() & 0xFF) / (float)RAND_MAX;
|
||||
}
|
||||
|
||||
// Pass 1: reduce the input float vector to one partial sum per block.
|
||||
__global__ void reduce(float *inputVec, double *outputVec, size_t inputSize, size_t outputSize)
|
||||
{
|
||||
typedef cub::BlockReduce<double, THREADS_PER_BLOCK> BlockReduceT;
|
||||
__shared__ typename BlockReduceT::TempStorage temp_storage;
|
||||
|
||||
size_t globaltid = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
|
||||
// Each thread adds its assigned elements into a local partial sum
|
||||
double temp_sum = 0.0;
|
||||
for (size_t i = globaltid; i < inputSize; i += (size_t)gridDim.x * blockDim.x)
|
||||
temp_sum += (double)inputVec[i];
|
||||
|
||||
// CUB reduces all per-thread partial sums to a single block sum; result lands on thread 0
|
||||
double block_sum = BlockReduceT(temp_storage).Sum(temp_sum);
|
||||
|
||||
if (threadIdx.x == 0 && blockIdx.x < outputSize)
|
||||
outputVec[blockIdx.x] = block_sum;
|
||||
}
|
||||
|
||||
// Pass 2: reduce the per-block partial sums to a single scalar result.
|
||||
__global__ void reduceFinal(double *inputVec, double *result, size_t inputSize)
|
||||
{
|
||||
typedef cub::BlockReduce<double, THREADS_PER_BLOCK> BlockReduceT;
|
||||
__shared__ typename BlockReduceT::TempStorage temp_storage;
|
||||
|
||||
size_t globaltid = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
|
||||
// Each thread adds its assigned elements into a local partial sum
|
||||
double temp_sum = 0.0;
|
||||
for (size_t i = globaltid; i < inputSize; i += (size_t)gridDim.x * blockDim.x)
|
||||
temp_sum += (double)inputVec[i];
|
||||
|
||||
// CUB reduces all per-thread partial sums to a single block sum; result lands on thread 0
|
||||
double block_sum = BlockReduceT(temp_storage).Sum(temp_sum);
|
||||
|
||||
if (threadIdx.x == 0)
|
||||
result[0] = block_sum;
|
||||
}
|
||||
156
cpp/3_CUDA_Features/simpleCudaGraphs/simpleCudaGraphs_capture.cu
Normal file
156
cpp/3_CUDA_Features/simpleCudaGraphs/simpleCudaGraphs_capture.cu
Normal file
@@ -0,0 +1,156 @@
|
||||
/* Copyright (c) 2026, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Demonstrates CUDA Graph construction via stream capture.
|
||||
*
|
||||
* Operations are issued to a stream between cudaStreamBeginCapture and
|
||||
* cudaStreamEndCapture. Instead of executing, they are recorded and the
|
||||
* runtime automatically builds this graph:
|
||||
*
|
||||
* memcpy (H2D) -> reduce -> reduceFinal -> memcpy (D2H) -> host callback
|
||||
*
|
||||
* The workload is a two-pass reduction: an input float vector is reduced to
|
||||
* per-block partial sums (reduce), then to a single double result (reduceFinal).
|
||||
* The graph is instantiated once and launched GRAPH_LAUNCH_ITERATIONS times,
|
||||
* then cloned and launched again.
|
||||
*/
|
||||
|
||||
#include "simpleCudaGraphs.cuh"
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <cstdio>
|
||||
|
||||
#define GRAPH_LAUNCH_ITERATIONS 3
|
||||
|
||||
void cudaGraphsUsingStreamCapture(float *inputVec_h,
|
||||
float *inputVec_d,
|
||||
double *outputVec_d,
|
||||
double *result_d,
|
||||
size_t inputSize,
|
||||
size_t numOfBlocks)
|
||||
{
|
||||
cudaStream_t stream1, streamForGraph;
|
||||
cudaGraph_t graph;
|
||||
double result_h = 0.0;
|
||||
|
||||
cudaStreamCreate(&stream1);
|
||||
cudaStreamCreate(&streamForGraph);
|
||||
|
||||
// Begin capture: operations issued to stream1 are recorded, not executed.
|
||||
cudaStreamBeginCapture(stream1, cudaStreamCaptureModeGlobal);
|
||||
|
||||
cudaMemcpyAsync(inputVec_d, inputVec_h, sizeof(float) * inputSize, cudaMemcpyDefault, stream1);
|
||||
|
||||
reduce<<<numOfBlocks, THREADS_PER_BLOCK, 0, stream1>>>(inputVec_d, outputVec_d, inputSize, numOfBlocks);
|
||||
|
||||
reduceFinal<<<1, THREADS_PER_BLOCK, 0, stream1>>>(outputVec_d, result_d, numOfBlocks);
|
||||
cudaMemcpyAsync(&result_h, result_d, sizeof(double), cudaMemcpyDefault, stream1);
|
||||
|
||||
callBackData_t hostFnData = {0};
|
||||
hostFnData.data = &result_h;
|
||||
hostFnData.fn_name = "cudaGraphsUsingStreamCapture";
|
||||
cudaLaunchHostFunc(stream1, myHostNodeCallback, &hostFnData);
|
||||
|
||||
// End capture: the runtime builds a graph from everything recorded above.
|
||||
// The resulting graph is structurally identical to the one built manually.
|
||||
cudaStreamEndCapture(stream1, &graph);
|
||||
|
||||
size_t numNodes = 0;
|
||||
cudaGraphGetNodes(graph, NULL, &numNodes);
|
||||
printf("Graph node count: %zu\n", numNodes);
|
||||
|
||||
// Instantiate the captured graph into an executable form.
|
||||
cudaGraphExec_t graphExec;
|
||||
cudaGraphInstantiate(&graphExec, graph, NULL, NULL, 0);
|
||||
|
||||
// Demonstrates cudaGraphClone — in practice, cloning is useful when multiple CPU
|
||||
// threads need to launch the same graph concurrently, each with its own independent graphExec.
|
||||
cudaGraph_t clonedGraph;
|
||||
cudaGraphExec_t clonedGraphExec;
|
||||
cudaGraphClone(&clonedGraph, graph);
|
||||
cudaGraphInstantiate(&clonedGraphExec, clonedGraph, NULL, NULL, 0);
|
||||
|
||||
// Refill the host input before each launch so the graph's H2D copy processes
|
||||
// new data every time — one instantiated graph reused for different data.
|
||||
// The per-iteration sync ensures that copy finishes before we overwrite the buffer.
|
||||
for (int i = 0; i < GRAPH_LAUNCH_ITERATIONS; i++) {
|
||||
init_input(inputVec_h, inputSize);
|
||||
cudaGraphLaunch(graphExec, streamForGraph);
|
||||
cudaStreamSynchronize(streamForGraph);
|
||||
}
|
||||
|
||||
printf("\nCloned graph:\n");
|
||||
for (int i = 0; i < GRAPH_LAUNCH_ITERATIONS; i++) {
|
||||
init_input(inputVec_h, inputSize);
|
||||
cudaGraphLaunch(clonedGraphExec, streamForGraph);
|
||||
cudaStreamSynchronize(streamForGraph);
|
||||
}
|
||||
|
||||
cudaGraphExecDestroy(graphExec);
|
||||
cudaGraphExecDestroy(clonedGraphExec);
|
||||
cudaGraphDestroy(graph);
|
||||
cudaGraphDestroy(clonedGraph);
|
||||
cudaStreamDestroy(stream1);
|
||||
cudaStreamDestroy(streamForGraph);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
size_t size = 1 << 24; // number of elements to reduce, 16M elements
|
||||
size_t maxBlocks = 512;
|
||||
|
||||
int devID = 0;
|
||||
cudaSetDevice(devID);
|
||||
|
||||
int major, minor, smCount;
|
||||
cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, devID);
|
||||
cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, devID);
|
||||
cudaDeviceGetAttribute(&smCount, cudaDevAttrMultiProcessorCount, devID);
|
||||
printf("GPU Device %d: compute capability %d.%d, %d SMs\n\n", devID, major, minor, smCount);
|
||||
|
||||
printf("Reducing %zu elements\n", size);
|
||||
printf("Threads per block : %d\n", THREADS_PER_BLOCK);
|
||||
printf("Graph launch iterations: %d\n\n", GRAPH_LAUNCH_ITERATIONS);
|
||||
|
||||
float *inputVec_h = NULL, *inputVec_d = NULL;
|
||||
double *outputVec_d = NULL, *result_d = NULL;
|
||||
|
||||
cudaMallocHost(&inputVec_h, sizeof(float) * size);
|
||||
cudaMalloc(&inputVec_d, sizeof(float) * size);
|
||||
cudaMalloc(&outputVec_d, sizeof(double) * maxBlocks);
|
||||
cudaMalloc(&result_d, sizeof(double));
|
||||
|
||||
printf("=== Stream Capture ===\n");
|
||||
cudaGraphsUsingStreamCapture(inputVec_h, inputVec_d, outputVec_d, result_d, size, maxBlocks);
|
||||
|
||||
cudaFree(inputVec_d);
|
||||
cudaFree(outputVec_d);
|
||||
cudaFree(result_d);
|
||||
cudaFreeHost(inputVec_h);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/* Copyright (c) 2026, 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Demonstrates explicit CUDA Graph construction using the unified cudaGraphAddNode API.
|
||||
*
|
||||
* Nodes and edges are added one at a time to build this graph:
|
||||
*
|
||||
* memcpy (H2D) -> reduce -> reduceFinal -> memcpy (D2H) -> host callback
|
||||
*
|
||||
* The workload is a two-pass reduction: an input float vector is reduced to
|
||||
* per-block partial sums (reduce), then to a single double result (reduceFinal).
|
||||
* The graph is instantiated once and launched GRAPH_LAUNCH_ITERATIONS times,
|
||||
* then cloned and launched again.
|
||||
*/
|
||||
|
||||
#include "simpleCudaGraphs.cuh"
|
||||
|
||||
#include <cuda_runtime.h>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
#define GRAPH_LAUNCH_ITERATIONS 3
|
||||
|
||||
void cudaGraphsManual(float *inputVec_h,
|
||||
float *inputVec_d,
|
||||
double *outputVec_d,
|
||||
double *result_d,
|
||||
size_t inputSize,
|
||||
size_t numOfBlocks)
|
||||
{
|
||||
cudaStream_t streamForGraph;
|
||||
cudaGraph_t graph;
|
||||
std::vector<cudaGraphNode_t> nodeDependencies;
|
||||
cudaGraphNode_t memcpyNode, kernelNode;
|
||||
double result_h = 0.0;
|
||||
|
||||
cudaStreamCreate(&streamForGraph);
|
||||
|
||||
// cudaGraphAddNode is the unified node-creation API: one call for every node type,
|
||||
// driven by a cudaGraphNodeParams struct (a .type tag + a union of per-type params).
|
||||
// It replaces the previously used cudaGraphAddMemcpyNode/cudaGraphAddKernelNode calls. The struct
|
||||
// has reserved fields that must be zero, so it is re-zeroed with "= {}" before each node below.
|
||||
cudaGraphNodeParams nodeParams = {};
|
||||
cudaMemcpy3DParms memcpyParams = {0};
|
||||
|
||||
memcpyParams.srcArray = NULL;
|
||||
memcpyParams.srcPos = make_cudaPos(0, 0, 0);
|
||||
memcpyParams.srcPtr = make_cudaPitchedPtr(inputVec_h, sizeof(float) * inputSize, inputSize, 1);
|
||||
memcpyParams.dstArray = NULL;
|
||||
memcpyParams.dstPos = make_cudaPos(0, 0, 0);
|
||||
memcpyParams.dstPtr = make_cudaPitchedPtr(inputVec_d, sizeof(float) * inputSize, inputSize, 1);
|
||||
memcpyParams.extent = make_cudaExtent(sizeof(float) * inputSize, 1, 1);
|
||||
memcpyParams.kind = cudaMemcpyHostToDevice;
|
||||
|
||||
// Create an empty graph; nodes and edges will be added below.
|
||||
cudaGraphCreate(&graph, 0);
|
||||
|
||||
// Node 1: H2D memcpy — no dependencies (NULL, 0), so it can start immediately.
|
||||
// For a memcpy node the cudaMemcpy3DParms goes into nodeParams.memcpy.copyParams
|
||||
// (the .memcpy union member is a wrapper struct, so the copy descriptor nests one level deeper).
|
||||
nodeParams = {};
|
||||
nodeParams.type = cudaGraphNodeTypeMemcpy;
|
||||
nodeParams.memcpy.copyParams = memcpyParams;
|
||||
cudaGraphAddNode(&memcpyNode, graph, NULL, /*dependencyData=*/NULL, 0, &nodeParams);
|
||||
|
||||
// Make the next node wait for this memcpy to finish before starting.
|
||||
nodeDependencies.push_back(memcpyNode);
|
||||
|
||||
void *kernelArgs[4] = {(void *)&inputVec_d, (void *)&outputVec_d, &inputSize, &numOfBlocks};
|
||||
|
||||
// Node 2: first reduction kernel — depends on the H2D memcpy completing.
|
||||
// Kernel params are set directly on the .kernel union member.
|
||||
nodeParams = {};
|
||||
nodeParams.type = cudaGraphNodeTypeKernel;
|
||||
nodeParams.kernel.func = (void *)reduce;
|
||||
nodeParams.kernel.gridDim = dim3(numOfBlocks, 1, 1);
|
||||
nodeParams.kernel.blockDim = dim3(THREADS_PER_BLOCK, 1, 1);
|
||||
nodeParams.kernel.sharedMemBytes = 0;
|
||||
nodeParams.kernel.kernelParams = (void **)kernelArgs;
|
||||
nodeParams.kernel.extra = NULL;
|
||||
cudaGraphAddNode(
|
||||
&kernelNode, graph, nodeDependencies.data(), /*dependencyData=*/NULL, nodeDependencies.size(), &nodeParams);
|
||||
|
||||
// Move dependency forward: the next node waits for this kernel.
|
||||
nodeDependencies.clear();
|
||||
nodeDependencies.push_back(kernelNode);
|
||||
|
||||
void *kernelArgs2[3] = {(void *)&outputVec_d, (void *)&result_d, &numOfBlocks};
|
||||
|
||||
// Node 3: final reduction kernel — depends on Node 2.
|
||||
nodeParams = {};
|
||||
nodeParams.type = cudaGraphNodeTypeKernel;
|
||||
nodeParams.kernel.func = (void *)reduceFinal;
|
||||
nodeParams.kernel.gridDim = dim3(1, 1, 1);
|
||||
nodeParams.kernel.blockDim = dim3(THREADS_PER_BLOCK, 1, 1);
|
||||
nodeParams.kernel.sharedMemBytes = 0;
|
||||
nodeParams.kernel.kernelParams = kernelArgs2;
|
||||
nodeParams.kernel.extra = NULL;
|
||||
cudaGraphAddNode(
|
||||
&kernelNode, graph, nodeDependencies.data(), /*dependencyData=*/NULL, nodeDependencies.size(), &nodeParams);
|
||||
nodeDependencies.clear();
|
||||
nodeDependencies.push_back(kernelNode);
|
||||
|
||||
memset(&memcpyParams, 0, sizeof(memcpyParams));
|
||||
|
||||
memcpyParams.srcArray = NULL;
|
||||
memcpyParams.srcPos = make_cudaPos(0, 0, 0);
|
||||
memcpyParams.srcPtr = make_cudaPitchedPtr(result_d, sizeof(double), 1, 1);
|
||||
memcpyParams.dstArray = NULL;
|
||||
memcpyParams.dstPos = make_cudaPos(0, 0, 0);
|
||||
memcpyParams.dstPtr = make_cudaPitchedPtr(&result_h, sizeof(double), 1, 1);
|
||||
memcpyParams.extent = make_cudaExtent(sizeof(double), 1, 1);
|
||||
memcpyParams.kind = cudaMemcpyDeviceToHost;
|
||||
|
||||
// Node 4: D2H memcpy — copies the scalar result back to the host.
|
||||
// Again the copy descriptor nests in nodeParams.memcpy.copyParams.
|
||||
nodeParams = {};
|
||||
nodeParams.type = cudaGraphNodeTypeMemcpy;
|
||||
nodeParams.memcpy.copyParams = memcpyParams;
|
||||
cudaGraphAddNode(&memcpyNode, graph, nodeDependencies.data(), /*dependencyData=*/NULL, nodeDependencies.size(), &nodeParams);
|
||||
nodeDependencies.clear();
|
||||
nodeDependencies.push_back(memcpyNode);
|
||||
|
||||
cudaGraphNode_t hostNode;
|
||||
callBackData_t hostFnData;
|
||||
hostFnData.data = &result_h;
|
||||
hostFnData.fn_name = "cudaGraphsManual";
|
||||
|
||||
// Node 5: host callback — runs on the CPU after the D2H copy completes.
|
||||
// The .host member is cudaHostNodeParamsV2 (fn + userData, plus a syncMode field
|
||||
// left at 0 by the zero-init above).
|
||||
nodeParams = {};
|
||||
nodeParams.type = cudaGraphNodeTypeHost;
|
||||
nodeParams.host.fn = myHostNodeCallback;
|
||||
nodeParams.host.userData = &hostFnData;
|
||||
cudaGraphAddNode(&hostNode, graph, nodeDependencies.data(), /*dependencyData=*/NULL, nodeDependencies.size(), &nodeParams);
|
||||
|
||||
size_t numNodes = 0;
|
||||
cudaGraphGetNodes(graph, NULL, &numNodes);
|
||||
printf("Graph node count: %zu\n", numNodes);
|
||||
|
||||
// Instantiate: compile the graph into an executable form (one-time cost).
|
||||
// This is where CUDA optimizes the schedule; repeated launches reuse this.
|
||||
cudaGraphExec_t graphExec;
|
||||
cudaGraphInstantiate(&graphExec, graph, NULL, NULL, 0);
|
||||
|
||||
// Demonstrates cudaGraphClone — in practice, cloning is useful when multiple CPU
|
||||
// threads need to launch the same graph concurrently, each with its own independent graphExec.
|
||||
cudaGraph_t clonedGraph;
|
||||
cudaGraphExec_t clonedGraphExec;
|
||||
cudaGraphClone(&clonedGraph, graph);
|
||||
cudaGraphInstantiate(&clonedGraphExec, clonedGraph, NULL, NULL, 0);
|
||||
|
||||
// Refill the host input before each launch so the graph's H2D copy processes
|
||||
// new data every time — one instantiated graph reused for different data.
|
||||
// The per-iteration sync ensures that copy finishes before we overwrite the buffer.
|
||||
for (int i = 0; i < GRAPH_LAUNCH_ITERATIONS; i++) {
|
||||
init_input(inputVec_h, inputSize);
|
||||
cudaGraphLaunch(graphExec, streamForGraph);
|
||||
cudaStreamSynchronize(streamForGraph);
|
||||
}
|
||||
|
||||
printf("\nCloned graph:\n");
|
||||
for (int i = 0; i < GRAPH_LAUNCH_ITERATIONS; i++) {
|
||||
init_input(inputVec_h, inputSize);
|
||||
cudaGraphLaunch(clonedGraphExec, streamForGraph);
|
||||
cudaStreamSynchronize(streamForGraph);
|
||||
}
|
||||
|
||||
cudaGraphExecDestroy(graphExec);
|
||||
cudaGraphExecDestroy(clonedGraphExec);
|
||||
cudaGraphDestroy(graph);
|
||||
cudaGraphDestroy(clonedGraph);
|
||||
cudaStreamDestroy(streamForGraph);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
size_t size = 1 << 24; // number of elements to reduce, 16M elements
|
||||
size_t maxBlocks = 512;
|
||||
|
||||
int devID = 0;
|
||||
cudaSetDevice(devID);
|
||||
|
||||
int major, minor, smCount;
|
||||
cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, devID);
|
||||
cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, devID);
|
||||
cudaDeviceGetAttribute(&smCount, cudaDevAttrMultiProcessorCount, devID);
|
||||
printf("GPU Device %d: compute capability %d.%d, %d SMs\n\n", devID, major, minor, smCount);
|
||||
|
||||
printf("Reducing %zu elements\n", size);
|
||||
printf("Threads per block : %d\n", THREADS_PER_BLOCK);
|
||||
printf("Graph launch iterations: %d\n\n", GRAPH_LAUNCH_ITERATIONS);
|
||||
|
||||
float *inputVec_h = NULL, *inputVec_d = NULL;
|
||||
double *outputVec_d = NULL, *result_d = NULL;
|
||||
|
||||
cudaMallocHost(&inputVec_h, sizeof(float) * size);
|
||||
cudaMalloc(&inputVec_d, sizeof(float) * size);
|
||||
cudaMalloc(&outputVec_d, sizeof(double) * maxBlocks);
|
||||
cudaMalloc(&result_d, sizeof(double));
|
||||
|
||||
printf("=== Explicit Graph Construction ===\n");
|
||||
cudaGraphsManual(inputVec_h, inputVec_d, outputVec_d, result_d, size, maxBlocks);
|
||||
|
||||
cudaFree(inputVec_d);
|
||||
cudaFree(outputVec_d);
|
||||
cudaFree(result_d);
|
||||
cudaFreeHost(inputVec_h);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user