CUDA 进阶应用与测试实战笔记 + C++示例代码(第十章重点)
一、核心笔记(整合第十章核心)
1. 现有 GPU 库与框架使用
核心库介绍
-
cuBLAS:NVIDIA 官方线性代数库(BLAS 标准实现),优化程度极高,支持矩阵乘法、向量运算等,适合科学计算、AI 领域。
-
关键特性:默认列优先存储(Fortran 风格),C/C++行优先数据需通过`CUBLAS_OP_T`(转置)适配;
-
核心函数:
cublasSgemm(单精度矩阵乘法)、cublasDgemm(双精度),需手动管理 GPU 内存和数据传输。
-
-
Thrust:类 C++ STL 的并行算法库,封装 CUDA 底层细节,简化 GPU 编程。
-
核心功能:向量操作、排序、归约、扫描等;
-
优势:语法贴近 STL,自动管理 GPU 内存(
thrust::device_vector),适合快速开发和原型验证。
-
2. 自定义内核 vs 使用现有库
二、C++(CUDA)示例代码
示例 1:cuBLAS 矩阵乘法(列优先适配+转置处理)
核心功能
使用 cuBLAS 实现矩阵乘法,处理列优先存储与行优先数据的适配,呼应第十章“cuBLAS 使用”核心知识点。
#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
// 错误检查宏
#define CHECK_CUDA_ERR(err) \
if (err != cudaSuccess) { \
cerr << "CUDA错误:" << cudaGetErrorString(err) << "(行号:" << __LINE__ << ")" << endl; \
exit(1); \
}
#define CHECK_CUBLAS_ERR(err) \
if (err != CUBLAS_STATUS_SUCCESS) { \
cerr << "cuBLAS错误:" << err << "(行号:" << __LINE__ << ")" << endl; \
exit(1); \
}
int main() {
const int N = 1024; // 矩阵尺寸(1024×1024)
const size_t size = N * N * sizeof(float);
// 1. 主机内存分配与初始化(行优先存储)
vector<float> h_A(N * N, 1.0f); // 矩阵A全1
vector<float> h_B(N * N, 2.0f); // 矩阵B全2
vector<float> h_C(N * N, 0.0f); // 结果矩阵C
vector<float> h_C_ref(N * N, 0.0f); // CPU计算的参考结果
// 2. CPU计算参考结果(验证cuBLAS正确性)
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
for (int k = 0; k < N; ++k) {
h_C_ref[i * N + j] += h_A[i * N + k] * h_B[k * N + j];
}
}
}
// 3. GPU内存分配
float *d_A, *d_B, *d_C, *d_C_fixed;
CHECK_CUDA_ERR(cudaMalloc((void**)&d_A, size));
CHECK_CUDA_ERR(cudaMalloc((void**)&d_B, size));
CHECK_CUDA_ERR(cudaMalloc((void**)&d_C, size));
CHECK_CUDA_ERR(cudaMalloc((void**)&d_C_fixed, size));
// 4. 数据拷贝(主机→设备)
CHECK_CUDA_ERR(cudaMemcpy(d_A, h_A.data(), size, cudaMemcpyHostToDevice));
CHECK_CUDA_ERR(cudaMemcpy(d_B, h_B.data(), size, cudaMemcpyHostToDevice));
// 5. cuBLAS初始化与矩阵乘法
cublasHandle_t cublas_handle;
CHECK_CUBLAS_ERR(cublasCreate(&cublas_handle));
const float alpha = 1.0f;
const float beta = 0.0f;
// cuBLAS_Sgemm参数说明:
// 转置标识(CUBLAS_OP_T=转置)、矩阵维度、alpha、输入矩阵、leading dimension、beta、输出矩阵
CHECK_CUBLAS_ERR(cublasSgemm(
cublas_handle,
CUBLAS_OP_T, CUBLAS_OP_T, // A和B均转置(适配列优先存储)
N, N, N, // C(N×N) = A(N×N) × B(N×N)
&alpha,
d_A, N, // A的leading dimension(行优先时为N)
d_B, N, // B的leading dimension
&beta,
d_C, N // 输出矩阵C(列优先存储)
));
// 6. 转置C为行优先(适配C++存储)
CHECK_CUBLAS_ERR(cublasSgeam(
cublas_handle,
CUBLAS_OP_T, CUBLAS_OP_N, // 仅转置d_C
N, N,
&alpha,
d_C, N,
&beta,
nullptr, N,
d_C_fixed, N
));
// 7. 数据拷贝(设备→主机)
CHECK_CUDA_ERR(cudaMemcpy(h_C.data(), d_C_fixed, size, cudaMemcpyDeviceToHost));
// 8. 结果验证(前10个元素)
bool valid = true;
for (int i = 0; i < 10; ++i) {
if (abs(h_C[i] - h_C_ref[i]) > 1e-5) {
valid = false;
break;
}
}
cout << "cuBLAS矩阵乘法结果验证:" << (valid ? "正确" : "错误") << endl;
cout << "前10个元素参考值:";
for (int i = 0; i < 10; ++i) cout << h_C_ref[i] << " ";
cout << endl;
cout << "前10个元素cuBLAS结果:";
for (int i = 0; i < 10; ++i) cout << h_C[i] << " ";
cout << endl;
// 9. 资源释放
CHECK_CUBLAS_ERR(cublasDestroy(cublas_handle));
CHECK_CUDA_ERR(cudaFree(d_A));
CHECK_CUDA_ERR(cudaFree(d_B));
CHECK_CUDA_ERR(cudaFree(d_C));
CHECK_CUDA_ERR(cudaFree(d_C_fixed));
return 0;
}
示例 2:Thrust 库排序(简化 GPU 编程)
核心功能
使用 Thrust 库实现 GPU 排序,呼应第十章“Thrust 并行算法库”知识点,对比 CPU 排序性能。
#include <<thrust/device_vector.h>
#include <<thrust/sort.h>
#include <<thrust/host_vector.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <algorithm>
using namespace std;
using namespace chrono;
int main() {
const int N = 33'554'432; // 3200万元素(书中测试规模)
cout << "排序元素数量:" << N << endl;
// 1. 主机内存初始化(随机浮点数)
host_vector<float> h_data(N);
generate(h_data.begin(), h_data.end(), []() { return rand() / (float)RAND_MAX; });
vector<float> h_data_cpu = h_data; // CPU排序用副本
// 2. CPU排序(STL,单线程)
auto cpu_start = high_resolution_clock::now();
sort(h_data_cpu.begin(), h_data_cpu.end());
auto cpu_time = duration_cast<milliseconds>(high_resolution_clock::now() - cpu_start).count();
cout << "CPU排序耗时:" << cpu_time << " ms" << endl;
// 3. Thrust GPU排序
auto gpu_start = high_resolution_clock::now();
// 主机→设备数据拷贝(thrust::device_vector自动管理GPU内存)
device_vector<float> d_data = h_data;
// GPU排序(类似STL sort,自动优化)
thrust::sort(d_data.begin(), d_data.end());
// 设备→主机数据拷贝
thrust::copy(d_data.begin(), d_data.end(), h_data.begin());
auto gpu_time = duration_cast<milliseconds>(high_resolution_clock::now() - gpu_start).count();
cout << "GPU排序(Thrust)总耗时(含传输):" << gpu_time << " ms" << endl;
// 4. 结果验证(前10个元素)
bool valid = true;
for (int i = 0; i < 10; ++i) {
if (abs(h_data[i] - h_data_cpu[i]) > 1e-5) {
valid = false;
break;
}
}
cout << "Thrust排序结果验证:" << (valid ? "正确" : "错误") << endl;
return 0;
}
示例 3:GTest 测试 CUDA 向量加法(C++级测试)
3.1 项目结构
cuda_vector_add_test/ ├── include/ │ └── vector_add.h # 向量加法头文件 ├── src/ │ └── vector_add.cu # CUDA向量加法实现 ├── test/ │ └── test_vector_add.cpp # GTest测试代码 └── CMakeLists.txt # 编译配置
3.2 头文件(include/vector_add.h)
#ifndef VECTOR_ADD_H
#define VECTOR_ADD_H
extern "C" {
/**
* @brief CUDA向量加法(主机端接口)
* @param a 输入向量a(行优先,主机内存)
* @param b 输入向量b(行优先,主机内存)
* @param c 输出向量c(行优先,主机内存)
* @param N 向量长度
*/
void vectorAdd(const float* a, const float* b, float* c, int N);
}
#endif
3.3 CUDA 实现(src/vector_add.cu)
#include "../include/vector_add.h"
#include <cuda_runtime.h>
#define CHECK_CUDA_ERR(err) \
if (err != cudaSuccess) { \
fprintf(stderr, "CUDA错误:%s(行号:%d)\n", cudaGetErrorString(err), __LINE__); \
exit(1); \
}
__global__ void vectorAddKernel(const float* d_a, const float* d_b, float* d_c, int N) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < N) {
d_c[idx] = d_a[idx] + d_b[idx];
}
}
void vectorAdd(const float* a, const float* b, float* c, int N) {
float *d_a, *d_b, *d_c;
size_t size = N * sizeof(float);
CHECK_CUDA_ERR(cudaMalloc((void**)&d_a, size));
CHECK_CUDA_ERR(cudaMalloc((void**)&d_b, size));
CHECK_CUDA_ERR(cudaMalloc((void**)&d_c, size));
CHECK_CUDA_ERR(cudaMemcpy(d_a, a, size, cudaMemcpyHostToDevice));
CHECK_CUDA_ERR(cudaMemcpy(d_b, b, size, cudaMemcpyHostToDevice));
int threadsPerBlock = 256;
int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
vectorAddKernel<<<blocksPerGrid, threadsPerBlock>>>(d_a, d_b, d_c, N);
CHECK_CUDA_ERR(cudaGetLastError());
CHECK_CUDA_ERR(cudaMemcpy(c, d_c, size, cudaMemcpyDeviceToHost));
CHECK_CUDA_ERR(cudaFree(d_a));
CHECK_CUDA_ERR(cudaFree(d_b));
CHECK_CUDA_ERR(cudaFree(d_c));
}
3.4 GTest 测试代码(test/test_vector_add.cpp)
#include <gtest/gtest.h>
#include "../include/vector_add.h"
// 测试套件:VectorAddTest,测试用例:SimpleAddition
TEST(VectorAddTest, SimpleAddition) {
int N = 5;
const float a[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
const float b[] = {10.0f, 20.0f, 30.0f, 40.0f, 50.0f};
float c[N] = {0.0f};
// 调用CUDA向量加法
vectorAdd(a, b, c, N);
// 验证结果(浮点型用EXPECT_FLOAT_EQ,允许微小误差)
for (int i = 0; i < N; ++i) {
EXPECT_FLOAT_EQ(c[i], a[i] + b[i]);
}
}
// 测试边界情况:空向量
TEST(VectorAddTest, EmptyVector) {
int N = 0;
float a[] = {};
float b[] = {};
float c[] = {};
vectorAdd(a, b, c, N); // 无崩溃即通过
SUCCEED();
}
// 测试大数据量(10000元素)
TEST(VectorAddTest, LargeVector) {
int N = 10000;
float* a = new float[N];
float* b = new float[N];
float* c = new float[N];
// 初始化数据
for (int i = 0; i < N; ++i) {
a[i] = i * 0.1f;
b[i] = (N - i) * 0.1f;
}
vectorAdd(a, b, c, N);
// 验证前10个和后10个元素
for (int i = 0; i < 10; ++i) {
EXPECT_NEAR(c[i], a[i] + b[i], 1e-5);
EXPECT_NEAR(c[N-10+i], a[N-10+i] + b[N-10+i], 1e-5);
}
delete[] a;
delete[] b;
delete[] c;
}
3.5 CMake 配置(CMakeLists.txt)
cmake_minimum_required(VERSION 3.18)
project(CUDAVectorAddTest LANGUAGES CXX CUDA)
# 启用测试(CTest)
enable_testing()
# 查找GTest库
find_package(GTest REQUIRED)
# 查找CUDA Toolkit
find_package(CUDAToolkit REQUIRED)
# 生成共享库(供测试和后续Python调用)
add_library(vector_add SHARED src/vector_add.cu)
set_target_properties(vector_add PROPERTIES
CUDA_SEPARABLE_COMPILATION ON
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)
target_include_directories(vector_add PUBLIC include)
target_link_libraries(vector_add PRIVATE CUDA::cudart)
# 生成测试可执行文件
add_executable(vector_add_test test/test_vector_add.cpp)
target_include_directories(vector_add_test PUBLIC include)
target_link_libraries(vector_add_test
GTest::GTest
GTest::Main
vector_add
CUDA::cudart
)
# 注册测试(CTest)
add_test(NAME VectorAddAllTests COMMAND vector_add_test)
示例 4:Pytest 测试 Python 调用 CUDA 代码(跨语言测试)
4.1 Python 包装模块(vector_add_py.py)
import ctypes
import numpy as np
# 加载CUDA共享库(编译后生成的libvector_add.so)
lib = ctypes.CDLL("../build/lib/libvector_add.so")
# 声明函数签名(适配NumPy数组)
lib.vectorAdd.argtypes = [
np.ctypeslib.ndpointer(dtype=np.float32, flags="C_CONTIGUOUS"),
np.ctypeslib.ndpointer(dtype=np.float32, flags="C_CONTIGUOUS"),
np.ctypeslib.ndpointer(dtype=np.float32, flags="C_CONTIGUOUS"),
ctypes.c_int
]
lib.vectorAdd.restype = None
def vector_add(a: np.ndarray, b: np.ndarray) -> np.ndarray:
"""Python包装函数:调用CUDA向量加法"""
# 验证输入(维度一致、float32、连续内存)
assert a.shape = b.shape, "输入向量维度不一致"
assert a.dtype = np.float32 and b.dtype = np.float32, "输入必须是float32类型"
assert a.flags["C_CONTIGUOUS"] and b.flags["C_CONTIGUOUS"], "输入必须是C连续内存"
N = a.size
c = np.empty_like(a) # 输出向量
lib.vectorAdd(a, b, c, N)
return c
4.2 Pytest 测试代码(test_vector_add_py.py)
import numpy as np
from vector_add_py import vector_add
def test_simple_addition():
"""测试简单向量加法"""
a = np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float32)
b = np.array([5.0, 6.0, 7.0, 8.0], dtype=np.float32)
expected = a + b
result = vector_add(a, b)
# 验证所有元素误差在1e-5内
np.testing.assert_allclose(result, expected, rtol=1e-5)
def test_large_vector():
"""测试大数据量向量加法"""
N = 1000000 # 100万元素
a = np.random.rand(N).astype(np.float32)
b = np.random.rand(N).astype(np.float32)
expected = a + b
result = vector_add(a, b)
np.testing.assert_allclose(result, expected, rtol=1e-5)
def test_empty_vector():
"""测试空向量"""
a = np.array([], dtype=np.float32)
b = np.array([], dtype=np.float32)
result = vector_add(a, b)
assert result.size = 0
def test_non_contiguous_vector():
"""测试非连续内存(应抛出断言错误)"""
a = np.array([1.0, 2.0, 3.0], dtype=np.float32)[::2] # 非连续切片
b = np.array([4.0, 5.0, 6.0], dtype=np.float32)[::2]
try:
vector_add(a, b)
except AssertionError as e:
assert "C连续内存" in str(e)
else:
assert False, "未抛出非连续内存断言错误"
三、关键说明
1. 核心知识点关联
-
示例 1(cuBLAS):演示现有库的高效使用,解决第十章“通用线性代数运算”场景,重点处理列优先与行优先的适配;
-
示例 2(Thrust):展示高层库简化 GPU 编程,呼应“快速开发与原型验证”需求,无需手动管理 GPU 内存;
-
示例 3(GTest):实现 C++级 CUDA 代码测试,确保自定义内核正确性,支持 CI/CD 集成;
-
示例 4(Pytest):跨语言测试 Python 调用 CUDA 共享库,覆盖第十章“代码多层面测试”要求。