使用 CUDA 图(CUDA Graph)来加速向量加法操作的示例。
--》〉该示例展示了如何通过 CUDA 图将一系列 CUDA 操作打包成一个图形,然后在 GPU 上执行这个图形,从而减少内核启动的开销。
#include <cuda_runtime.h>
#include <iostream>
#define N 1000000 // 向量大小
__global__ void vectorAdd(const float* A, const float* B, float* C, int numElements) {
int i = blockDim.x * blockIdx.x + threadIdx.x;
if (i < numElements) {
C[i] = A[i] + B[i];
}
}
int main() {
cudaStream_t stream;
cudaStreamCreate(&stream);
// 分配主机内存
float *h_A = (float*)malloc(N * sizeof(float));
float *h_B = (float*)malloc(N * sizeof(float));
float *h_C = (float*)malloc(N * sizeof(float));
// 初始化输入向量
for (int i = 0; i < N; ++i) {
h_A[i] = static_cast<float>(i);
h_B[i] = static_cast<float>(i * 2);
}
// 分配设备内存
float *d_A, *d_B, *d_C;
cudaMalloc((void**)&d_A, N * sizeof(float));
cudaMalloc((void**)&d_B, N * sizeof(float));
cudaMalloc((void**)&d_C, N * sizeof(float));
// 创建 CUDA 图
cudaGraph_t graph;
cudaGraphExec_t graphExec;
cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
// 将数据从主机传输到设备
cudaMemcpyAsync(d_A, h_A, N * sizeof(float), cudaMemcpyHostToDevice, stream);
cudaMemcpyAsync(d_B, h_B, N * sizeof(float), cudaMemcpyHostToDevice, stream);
// 执行向量加法内核
int threadsPerBlock = 256;
int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
vectorAdd<<<blocksPerGrid, threadsPerBlock, 0, stream>>>(d_A, d_B, d_C, N);
// 将结果从设备传回主机
cudaMemcpyAsync(h_C, d_C, N * sizeof(float), cudaMemcpyDeviceToHost, stream);
cudaStreamEndCapture(stream, &graph);
cudaGraphInstantiate(&graphExec, graph, NULL, NULL, 0);
// 执行图
cudaGraphLaunch(graphExec, stream);
cudaStreamSynchronize(stream);
// 验证结果
for (int i = 0; i < N; ++i) {
if (fabs(h_A[i] + h_B[i] - h_C[i]) > 1e-5) {
std::cerr << "Result verification failed at element " << i << std::endl;
exit(EXIT_FAILURE);
}
}
std::cout << "Test PASSED" << std::endl;
// 释放资源
cudaGraphExecDestroy(graphExec);
cudaGraphDestroy(graph);
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);
free(h_A);
free(h_B);
free(h_C);
cudaStreamDestroy(stream);
return 0;
}
代码说明:
1. 内核函数 vectorAdd: 执行向量加法操作,将输入向量 A 和 B 的对应元素相加,结果存储在向量 C 中。
2. 主函数 main:
• 创建 CUDA 流 stream。
• 分配主机和设备内存,并初始化输入向量。
• 使用 cudaStreamBeginCapture 开始捕获流中的操作,创建 CUDA 图 graph。
• 在捕获模式下,将数据从主机传输到设备,执行向量加法内核,并将结果从设备传回主机。
• 使用 cudaStreamEndCapture 结束捕获,生成图对象 graph。
• 实例化图,生成可执行图对象 graphExec。
• 使用 cudaGraphLaunch 执行可执行图,并同步流以等待执行完成。
• 验证计算结果的正确性。
• 释放所有分配的资源,包括图对象、设备内存、主机内存和流。
注意事项:
• 在捕获模式下,所有在流中提交的操作都会被记录到图中。
• 实例化图会对其进行验证,并为后续的快速执行做好准备。
• 执行图时,所有记录的操作将按照定义的依赖关系顺序执行。
• 在实际应用中,通常会多次执行相同的图,以充分利用 CUDA 图的性能优势。
通过使用 CUDA 图,可以减少内核启动的开销,提高 GPU 的利用率,特别是在需要多次执行相同操作序列的场景中。