0%

CUDA 常见函数

简介

  • CUDA 中常见的函数

cudaSetDevice() 函数 详解

cudaSetDevice() 函数详解

1. 作用

cudaSetDevice(int device) 用于设置当前 CPU 线程要使用的 GPU 设备。在一个系统中可能有多个 GPU,此函数可以让程序选择特定的 GPU 进行计算。

2. 语法

1
cudaError_t cudaSetDevice(int device);
  • device:要选择的 GPU 设备的索引(从 0 开始)。
  • 返回值:返回 cudaError_t 类型的错误码,若成功返回 cudaSuccess

3. 相关说明

  • 必须在调用任何 CUDA API(如 cudaMalloc()cudaMemcpy())之前调用 cudaSetDevice(),否则可能会导致错误或影响性能。
  • 每个 CPU 线程只能绑定一个 GPU 设备,因此 cudaSetDevice() 仅影响当前线程。
  • 如果没有调用 cudaSetDevice(),CUDA 运行时默认选择 device 0
  • 需要确保 device 值在可用的 GPU 范围内(可用 cudaGetDeviceCount() 获取 GPU 总数)。

4. 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <cuda_runtime.h>
#include <iostream>

int main() {
int deviceCount;
cudaGetDeviceCount(&deviceCount);
if (deviceCount == 0) {
std::cerr << "No CUDA devices available." << std::endl;
return -1;
}

int deviceId = 1; // 假设想使用第 1 块 GPU
if (deviceId >= deviceCount) {
std::cerr << "Invalid device ID." << std::endl;
return -1;
}

cudaError_t err = cudaSetDevice(deviceId);
if (err != cudaSuccess) {
std::cerr << "cudaSetDevice failed: " << cudaGetErrorString(err) << std::endl;
return -1;
}

std::cout << "Using GPU " << deviceId << std::endl;

return 0;
}

5. 获取当前设备

可以使用 cudaGetDevice() 获取当前线程绑定的 GPU:

1
2
3
int currentDevice;
cudaGetDevice(&currentDevice);
std::cout << "Current device: " << currentDevice << std::endl;

6. 线程与设备的关系

  • 单线程程序 中,每个 CPU 线程只能使用一个 GPU。
  • 多线程程序 中,不同的 CPU 线程可以绑定到不同的 GPU 以并行计算,但一个线程不能同时使用多个 GPU。

7. 可能的错误

错误代码 说明
cudaErrorInvalidDevice device 号超出 GPU 数量范围
cudaErrorSetOnActiveProcess 当前进程已经使用了另一个 GPU(在某些情况下)

8. 结合 cudaGetDeviceCount()

在调用 cudaSetDevice() 之前,建议使用 cudaGetDeviceCount() 确保 device 值合法:

1
2
3
4
5
int deviceCount;
cudaGetDeviceCount(&deviceCount);
if (deviceCount > 0) {
cudaSetDevice(0); // 选择第一个 GPU
}

9. 总结

  • cudaSetDevice() 用于选择 GPU,必须在 CUDA 相关 API 调用前使用。
  • 仅对当前 CPU 线程生效。
  • 在多 GPU 环境下,可结合 cudaGetDeviceCount()cudaGetDevice() 确保选择有效设备。

这样可以有效管理多 GPU 计算资源,提高 CUDA 程序的灵活性和性能。 🚀

cudaMalloc() 函数 详解

cudaMalloc() 函数详解

1. 作用

cudaMalloc() 用于在 GPU 设备(Device)上分配全局内存(Global Memory)。这是 CUDA 中最基础的内存管理函数之一。


2. 语法

1
cudaError_t cudaMalloc(void** devPtr, size_t size);

参数

  • devPtr:指向存储设备内存地址的指针变量(即指向 void* 的指针)。
  • size:要分配的内存大小(以字节为单位)。

返回值

  • cudaSuccess(0):分配成功。
  • 其他错误码(如 cudaErrorMemoryAllocation):分配失败。

3. 使用示例

(1) 在 GPU 设备上分配内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <cuda_runtime.h>
#include <iostream>

int main() {
int* d_ptr; // 设备指针
size_t size = 100 * sizeof(int); // 分配 100 个 int

cudaError_t err = cudaMalloc((void**)&d_ptr, size);
if (err != cudaSuccess) {
std::cerr << "cudaMalloc failed: " << cudaGetErrorString(err) << std::endl;
return -1;
}

std::cout << "Memory allocated on GPU." << std::endl;

// 释放设备内存
cudaFree(d_ptr);

return 0;
}

4. 相关注意事项

(1) 设备指针不能直接在主机(CPU)端访问

1
2
3
int* d_ptr;
cudaMalloc((void**)&d_ptr, 100 * sizeof(int));
std::cout << *d_ptr << std::endl; // ❌ 错误!设备指针不能直接解引用

正确做法:使用 cudaMemcpy() 将数据拷贝到主机:

1
2
3
int h_val;
cudaMemcpy(&h_val, d_ptr, sizeof(int), cudaMemcpyDeviceToHost);
std::cout << h_val << std::endl;

(2) 设备内存需要手动释放

  • 使用 cudaFree() 释放内存:
    1
    cudaFree(d_ptr);
  • 如果忘记释放,会导致 显存泄漏(Memory Leak)

(3) 检查 cudaMalloc() 是否成功

  • 可能的错误
    • cudaErrorMemoryAllocation:显存不足
    • cudaErrorInvalidValuesize 为 0
    • cudaErrorInvalidDevicePointer:传入的指针非法
  • 建议:始终检查返回值
    1
    2
    3
    if (cudaMalloc((void**)&d_ptr, size) != cudaSuccess) {
    std::cerr << "Memory allocation failed!" << std::endl;
    }

(4) 计算设备内存的使用情况

  • cudaMemGetInfo() 可用于查询 GPU 剩余内存:
    1
    2
    3
    4
    size_t freeMem, totalMem;
    cudaMemGetInfo(&freeMem, &totalMem);
    std::cout << "Free memory: " << freeMem / (1024 * 1024) << " MB, "
    << "Total memory: " << totalMem / (1024 * 1024) << " MB" << std::endl;

(5) 多 GPU 环境

  • cudaMalloc() 仅在当前设备上分配内存。
  • 若使用多 GPU,可先调用 cudaSetDevice(deviceId) 切换 GPU:
    1
    2
    cudaSetDevice(1);  // 选择 GPU 1
    cudaMalloc((void**)&d_ptr, size);

5. cudaMalloc() 与其他 CUDA 内存管理函数的比较

函数 作用 适用场景 释放方法
cudaMalloc() 设备全局内存分配 GPU 计算数据存储 cudaFree()
cudaMallocHost() 主机页锁定内存(可用于异步拷贝) cudaMemcpyAsync() cudaFreeHost()
cudaMallocManaged() 统一内存(主机和设备共享) 统一内存编程 cudaFree()
cudaMallocPitch() 2D 数组分配,优化对齐 处理图像等 2D 数据 cudaFree()

6. 总结

  • cudaMalloc() 在 GPU 上分配全局内存,返回设备指针。
  • 不能直接访问设备指针,需要使用 cudaMemcpy() 进行数据传输。
  • 需要手动释放 cudaFree(),否则会导致显存泄漏。
  • 在多 GPU 环境下,应先调用 cudaSetDevice() 选择目标 GPU。

合理使用 cudaMalloc() 进行显存管理,可以提高 CUDA 程序的性能和稳定性。 🚀

cudaStreamCreate() 函数 详解

cudaStreamCreate() 函数详解

1. 作用

cudaStreamCreate() 用于创建 CUDA 流(Stream),流是一种异步执行的队列,可以在同一 GPU 上同时执行多个任务,实现 并行计算异步执行


2. 语法

1
cudaError_t cudaStreamCreate(cudaStream_t* pStream);

参数

  • pStream:指向 cudaStream_t 类型的指针,用于存储创建的流的句柄。

返回值

  • cudaSuccess:流创建成功。
  • 其他 cudaError_t 错误码:创建失败,例如 内存不足CUDA 上下文问题

3. 使用示例

(1) 创建并使用 CUDA 流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <cuda_runtime.h>
#include <iostream>

int main() {
cudaStream_t stream;
cudaError_t err = cudaStreamCreate(&stream);

if (err != cudaSuccess) {
std::cerr << "cudaStreamCreate failed: " << cudaGetErrorString(err) << std::endl;
return -1;
}

std::cout << "CUDA Stream created successfully!" << std::endl;

// 使用流进行异步计算(示例代码略)

// 释放流
cudaStreamDestroy(stream);

return 0;
}

4. 相关函数

(1) cudaStreamDestroy() - 销毁 CUDA 流

每个创建的 CUDA 流在使用后应当释放:

1
cudaStreamDestroy(stream);

(2) cudaMemcpyAsync() - 异步拷贝

流可以与 cudaMemcpyAsync() 结合,实现 异步数据拷贝

1
2
3
4
5
int* d_data;
int h_data[100];
cudaMalloc((void**)&d_data, 100 * sizeof(int));

cudaMemcpyAsync(d_data, h_data, 100 * sizeof(int), cudaMemcpyHostToDevice, stream);

(3) cudaStreamSynchronize() - 等待流完成

使用 cudaStreamSynchronize() 可以等待特定流中的任务执行完毕:

1
cudaStreamSynchronize(stream);

5. CUDA 流的特点

(1) 默认流(cudaStream_t(0)

  • 如果 不指定流,CUDA 内核和 cudaMemcpy() 运行在 默认流(默认队列) 中,按顺序执行:
    1
    2
    3
    cudaMemcpy(d_data, h_data, 100 * sizeof(int), cudaMemcpyHostToDevice);
    kernel<<<grid, block>>>(d_data);
    cudaMemcpy(h_data, d_data, 100 * sizeof(int), cudaMemcpyDeviceToHost);
  • 这些操作会 按顺序执行,下一个任务必须等待上一个任务完成。

(2) 用户定义流(异步执行)

  • 使用 cudaStreamCreate() 创建 多个流,可在同一 GPU 设备上同时执行 多个任务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    cudaStream_t stream1, stream2;
    cudaStreamCreate(&stream1);
    cudaStreamCreate(&stream2);

    // 在 stream1 中执行内存拷贝
    cudaMemcpyAsync(d_data1, h_data1, size, cudaMemcpyHostToDevice, stream1);
    // 在 stream2 中执行不同的任务
    cudaMemcpyAsync(d_data2, h_data2, size, cudaMemcpyHostToDevice, stream2);

    // 等待两个流完成
    cudaStreamSynchronize(stream1);
    cudaStreamSynchronize(stream2);

    // 释放流
    cudaStreamDestroy(stream1);
    cudaStreamDestroy(stream2);
    在这种情况下,数据拷贝和计算可以并行执行,提高 GPU 利用率。

6. 使用多个 CUDA 流的应用场景

场景 说明
数据拷贝与计算重叠 cudaMemcpyAsync() 和 Kernel 在不同流中执行,提高效率
多 Kernel 并行执行 多个 Kernel 任务放入不同流,提高计算吞吐量
多线程任务调度 在多线程程序中,每个线程管理一个 CUDA 流

7. cudaStreamCreate() 可能的错误

错误码 说明
cudaErrorInvalidValue pStream 为空指针
cudaErrorMemoryAllocation 内存不足,无法创建流
cudaErrorInitializationError CUDA 运行时未正确初始化

8. 总结

  • cudaStreamCreate() 创建 CUDA 流,用于异步执行任务。
  • 任务可使用 cudaMemcpyAsync()cudaLaunchKernel() 提交到特定流。
  • 使用 cudaStreamSynchronize() 等待流完成,使用 cudaStreamDestroy() 释放资源。
  • 适用于 数据传输与计算重叠、Kernel 并行执行、多线程任务调度 等场景。

合理使用 CUDA 流,可以 提升 GPU 并行计算效率,加速深度学习、科学计算等应用!🚀

cudaMemcpyAsync() 函数 详解

cudaMemcpyAsync() 函数详解

1. 作用

cudaMemcpyAsync() 用于在 主机(Host)设备(Device) 之间进行 异步 数据拷贝,与 cudaMemcpy() 不同,它不会阻塞 CPU,而是将拷贝任务放入指定的 CUDA 流(Stream) 中,从而实现 数据传输与计算的重叠,提高 GPU 计算效率。


2. 语法

1
cudaError_t cudaMemcpyAsync(void* dst, const void* src, size_t count, cudaMemcpyKind kind, cudaStream_t stream);

参数

  • dst:目标地址(可以是主机或设备内存)。
  • src:源地址(可以是主机或设备内存)。
  • count:拷贝的数据大小(以字节为单位)。
  • kind:数据拷贝的方向,cudaMemcpyKind 枚举值:
    • cudaMemcpyHostToDevice(H→D):从主机拷贝到设备
    • cudaMemcpyDeviceToHost(D→H):从设备拷贝到主机
    • cudaMemcpyDeviceToDevice(D→D):在设备内存之间拷贝
    • cudaMemcpyHostToHost(H→H):在主机内存之间拷贝
  • stream:指定 CUDA ,数据拷贝任务将在该流中执行(可使用 0 代表 默认流)。

返回值

  • **cudaSuccess**:拷贝成功。
  • 其他 cudaError_t 错误码:如 cudaErrorInvalidValue(参数无效)。

3. 代码示例

(1) 基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <cuda_runtime.h>
#include <iostream>

int main() {
cudaStream_t stream;
cudaStreamCreate(&stream); // 创建 CUDA 流

int h_data[100], h_result[100]; // 主机数据
int* d_data; // 设备指针
size_t size = 100 * sizeof(int);

// 分配设备内存
cudaMalloc((void**)&d_data, size);

// 异步拷贝数据到设备
cudaMemcpyAsync(d_data, h_data, size, cudaMemcpyHostToDevice, stream);

// 异步拷贝数据回主机
cudaMemcpyAsync(h_result, d_data, size, cudaMemcpyDeviceToHost, stream);

// 等待流中任务完成
cudaStreamSynchronize(stream);

// 释放资源
cudaFree(d_data);
cudaStreamDestroy(stream);

std::cout << "Async memcpy completed!" << std::endl;
return 0;
}

cudaMemcpyAsync() 执行时,CPU 不会等待 数据拷贝完成,而是继续执行后续代码。只有当 cudaStreamSynchronize() 被调用时,才会阻塞 CPU 直到流中的任务完成。


4. cudaMemcpyAsync()cudaMemcpy() 的区别

特性 cudaMemcpy() cudaMemcpyAsync()
同步/异步 同步(阻塞 CPU) 异步(不阻塞 CPU)
默认流 使用默认流 0 可以指定流
计算与拷贝重叠 不支持 支持(需要 cudaMemcpyAsync() + cudaStreamSynchronize()
适用场景 简单数据拷贝 需要 并行执行计算 & 传输 的情况

5. cudaMemcpyAsync() 的高级应用

(1) 计算与数据拷贝重叠

cudaMemcpyAsync() 的主要优势是 可以与 Kernel 并行执行,提高 GPU 计算吞吐量。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <cuda_runtime.h>
#include <iostream>

__global__ void kernel(int* d_data) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
d_data[idx] *= 2;
}

int main() {
cudaStream_t stream;
cudaStreamCreate(&stream);

int h_data[100], h_result[100];
int* d_data;
size_t size = 100 * sizeof(int);

cudaMalloc((void**)&d_data, size);

// 异步拷贝数据到 GPU
cudaMemcpyAsync(d_data, h_data, size, cudaMemcpyHostToDevice, stream);

// 在同一流中启动 Kernel(异步执行)
kernel<<<1, 100, 0, stream>>>(d_data);

// 异步拷贝结果回主机
cudaMemcpyAsync(h_result, d_data, size, cudaMemcpyDeviceToHost, stream);

// 等待流完成
cudaStreamSynchronize(stream);

cudaFree(d_data);
cudaStreamDestroy(stream);
return 0;
}

在这里:

  1. 数据拷贝cudaMemcpyAsync()
  2. 计算 Kernel 执行kernel<<<>>>
  3. 拷贝结果回主机cudaMemcpyAsync()
    都在 同一流stream)中执行,CPU 不会等待,而是继续执行后续代码,最终 cudaStreamSynchronize() 才会阻塞 CPU,等待所有任务完成。

(2) 使用多个流并行执行任务

在多流(Multi-stream)环境下,多个 cudaMemcpyAsync() 可用于 并行数据传输

1
2
3
4
5
6
7
8
9
10
11
12
cudaStream_t stream1, stream2;
cudaStreamCreate(&stream1);
cudaStreamCreate(&stream2);

cudaMemcpyAsync(d_data1, h_data1, size, cudaMemcpyHostToDevice, stream1);
cudaMemcpyAsync(d_data2, h_data2, size, cudaMemcpyHostToDevice, stream2);

cudaStreamSynchronize(stream1);
cudaStreamSynchronize(stream2);

cudaStreamDestroy(stream1);
cudaStreamDestroy(stream2);
  • stream1 传输 d_data1,流 stream2 传输 d_data2,两个拷贝任务可同时进行。
  • 如果不使用流,CUDA 任务将会串行执行,不能充分利用 GPU 带宽。

6. cudaMemcpyAsync() 可能的错误

错误代码 说明
cudaErrorInvalidValue 参数无效(如 dstsrc 为空指针)
cudaErrorInvalidMemcpyDirection cudaMemcpyKind 不匹配(如 cudaMemcpyDeviceToHostdst 是设备指针)
cudaErrorMemoryAllocation 设备内存不足
cudaErrorIllegalAddress 访问了未分配或非法的 GPU 地址

7. cudaMemcpyAsync() 适用场景

应用场景 说明
数据传输与计算并行 异步拷贝 + Kernel 计算,减少 CPU/GPU 等待时间
多流任务调度 在不同流中执行不同的任务,提高 GPU 利用率
批量数据处理 预加载数据,提高数据吞吐量

8. 总结

  • cudaMemcpyAsync() 异步 进行数据拷贝,不会阻塞 CPU。
  • 需要 指定 CUDA 流,否则默认使用 0(默认流)。
  • 需要 配合 cudaStreamSynchronize() 确保数据正确性。
  • 适用于 数据传输 + 计算并行执行,提高 GPU 计算吞吐量。

合理使用 cudaMemcpyAsync() 可以 提升 CUDA 应用的性能,尤其在 深度学习、科学计算、大规模数据处理 等场景下表现突出!🚀

感谢老板支持!敬礼(^^ゞ