CUDA Programming GuideCUDA 编程指南中文图解
首页
阅读地图
1.1 Introduction
PDF 图版
  • 源代码仓库(GitHub)
  • CUDA Programming Guide Release 13.3
  • NVIDIA 官方在线目录
  • 本地原始 PDF
首页
阅读地图
1.1 Introduction
PDF 图版
  • 源代码仓库(GitHub)
  • CUDA Programming Guide Release 13.3
  • NVIDIA 官方在线目录
  • 本地原始 PDF
  • 5. Technical Appendices

    • 5.1 计算能力(Compute Capabilities)
    • 5.2 CUDA 环境变量(CUDA Environment Variables)
    • 5.3 C++ 语言支持(C++ Language Support)
    • 5.4 C/C++ 语言扩展(C/C++ Language Extensions)
    • 5.5 浮点计算(Floating-Point Computation)
    • 5.6 设备可调用 API 与内建函数(Device-Callable APIs and Intrinsics)
    • 5.7 CUDA C++ 内存模型(CUDA C++ Memory Model)
    • 5.8 CUDA C++ 执行模型(CUDA C++ Execution model)

5.8 CUDA C++ Execution Model(CUDA C++ 执行模型)

CUDA C++ 旨在为所有 device 执行线程提供并行前进进度保证(parallel forward progress),从而支持使用 CUDA C++ 并行化已有的 C++ 应用程序。

以下内容是对当前 ISO 国际标准 ISO/IEC 14882——C++ 编程语言草案中 [intro.progress] 一节的修改与扩展。CUDA C++ 编程语言是 C++ 编程语言的扩展;修改的部分会明确标出,并以粗体显示差异,其余部分均为新增内容。

标准中的前进进度定义(Forward progress definitions)
  • [intro.progress.7]:对于提供并发前进进度保证的执行线程,只要该线程尚未终止,实现就必须保证该线程最终会取得进展。

    **注 5:**无论其他执行线程(如果有)是否已经取得或正在取得进展,这一要求都适用。“最终满足”这一要求意味着该事件会在未指定但有限的时间内发生。

  • [intro.progress.9]:对于提供并行前进进度保证的执行线程,如果该线程尚未执行任何执行步骤,实现不必保证它最终会取得进展;一旦该线程执行过一个步骤,它就提供并发前进进度保证。

    **注 6:**这没有规定何时启动该执行线程;启动时间通常由创建该执行线程的实体规定。例如,一个提供并发前进进度保证、并从任务集合中按任意顺序逐个执行任务的执行线程,满足这些任务的并行前进进度要求。

5.8.1 Host threads(Host 线程)

由 host 实现创建、用于执行 main、std::thread 和 std::jthread 的执行线程所获得的前进进度,是 host 实现定义的行为 [intro.progress]。通用 host 实现应提供并发前进进度。

如果 host 实现提供并发前进进度(intro.progress.7),则 CUDA C++ 会为 device 线程提供并行前进进度(intro.progress.9)。

5.8.2 Device threads(Device 线程)

一旦 device 线程取得进展:

  • 如果它属于一个 Cooperative Grid,则其 grid 中的所有 device 线程最终都应取得进展。

  • 否则,其线程块集群中的所有 device 线程最终都应取得进展。

    **注:**其他线程块集群中的线程不保证最终取得进展。

    **注:**这意味着其线程块集群中的所有 device 线程最终都应取得进展。

按照如下内容修改 [intro.progress.1](修改部分以粗体显示):

实现可以假设任意 host 线程最终会执行以下操作之一:

  1. 终止;

  2. 调用函数 std::this_thread::yield([thread.thread.this]);

  3. 调用库 I/O 函数;

  4. 通过 volatile glvalue 执行访问;

  5. 执行同步操作或原子操作;或者

  6. 继续执行一个平凡的无限循环([stmt.iter.general])。

实现可以假设任意 device 线程最终会执行以下操作之一:

  1. 终止;

  2. 调用库 I/O 函数;

  3. 通过 volatile glvalue 执行访问,但指定对象具有自动存储期的情况除外;或者

  4. 执行同步操作或原子读取操作,但指定对象具有自动存储期的情况除外。

**注:**device 线程相对于 host 线程的一些当前限制,是 NVIDIA 已知的、未来可能修复的实现缺陷。例如,device 线程最终只对具有自动存储期的对象执行 volatile 操作或原子操作时产生的未定义行为,就是其中之一。不过,device 线程相对于 host 线程的其他限制是有意作出的选择。这些选择能够启用一些性能优化;如果 device 线程严格遵循 C++ 标准,这些优化将无法实现。例如,为最终只执行原子写入或栅栏的程序提供前进进度,会在实际收益很小的情况下降低总体性能。

由于修改 [intro.progress.1],host 与 device 线程的前进进度保证差异示例

以下示例使用上文 host 和 device 线程实现假设中的条款编号,分别记为“host.threads.<id>”和“device.threads.<id>”。代码中的行号和注释保留自官方示例。

1// Example: Execution.Model.Device.0
2// Outcome: grid eventually terminates per device.threads.4 because the atomic object does not have automatic storage duration.
3__global__ void ex0(cuda::atomic_ref<int, cuda::thread_scope_device> atom) {
4    if (threadIdx.x == 0) {
5        while(atom.load(cuda::memory_order_relaxed) == 0);
6    } else if (threadIdx.x == 1) {
7        atom.store(1, cuda::memory_order_relaxed);
8    }
9}
1// Example: Execution.Model.Device.1
2// Allowed outcome: No thread makes progress because device threads don't support host.threads.2.
3__global__ void ex1() {
4    while(true) cuda::std::this_thread::yield();
5}
1// Example: Execution.Model.Device.2
2// Allowed outcome: No thread makes progress because device threads don't support host.threads.4
3// for objects with automatic storage duration (see exception in device.threads.3).
4__global__ void ex2() {
5    volatile bool True = true;
6    while(True);
7}
1// Example: Execution.Model.Device.3
2// Allowed outcome: No thread makes progress because device threads don't support host.threads.5
3// for objects with automatic storage duration (see exception in device.threads.4).
4__global__ void ex3() {
5    cuda::atomic<bool, cuda::thread_scope_thread> True = true;
6    while(True.load());
7}
1// Example: Execution.Model.Device.4
2// Allowed outcome: No thread makes progress because device threads don't support host.thread.6.
3__global void ex4() {
4    while(true) { /* empty */ }
5}

5.8.3 CUDA APIs(CUDA API)

一次 CUDA API 调用最终必须返回,或者确保至少一个 device 线程取得进展。

CUDA 查询函数(例如 cudaStreamQuery、cudaEventQuery 等)不应在没有任何 device 线程取得进展的情况下持续返回 cudaErrorNotReady。

**注:**device 线程不必与 API 调用“相关”;例如,在一个 stream 或进程上操作的 API,可以确保另一个 stream 或进程上的 device 线程取得进展。

**注:**测试程序是否符合 CUDA API 前进进度的一种简单但并不充分的方法,是设置环境变量 CUDA_DEVICE_MAX_CONNECTIONS=1 和 CUDA_LAUNCH_BLOCKING=1 后运行程序,然后检查程序是否仍能终止。如果不能,程序存在错误。该方法并不充分,因为它无法捕获所有前进进度错误;但它能够捕获许多此类错误。

CUDA API 前进进度保证示例
     1// Example: Execution.Model.API.1
     2// Outcome: if no other device threads (e.g., from other processes) are making progress,
     3// this program terminates and returns cudaSuccess.
     4// Rationale: CUDA guarantees that if the device is empty:
     5// - `cudaDeviceSynchronize` eventually ensures that at least one device-thread makes progress, which implies that eventually `hello_world` grid and one of its device-threads start.
     6// - All thread-block threads eventually start (due to "if a device thread makes progress, all other threads in its thread-block cluster eventually make progress").
     7// - Once all threads in thread-block arrive at `__syncthreads` barrier, all waiting threads are unblocked.
     8// - Therefore all device threads eventually exit the `hello_world`` grid.
     9// - And `cudaDeviceSynchronize`` eventually unblocks.
    10__global__ void hello_world() { __syncthreads(); }
    11int main() {
    12    hello_world<<<1,2>>>();
    13    return (int)cudaDeviceSynchronize();
    14}
     1// Example: Execution.Model.API.2
     2// Allowed outcome: eventually, no thread makes progress.
     3// Rationale: the `cudaDeviceSynchronize` API below is only called if a device thread eventually makes progress and sets the flag.
     4// However, CUDA only guarantees that `producer` device thread eventually starts if the synchronization API is called.
     5// Therefore, the host thread may never be unblocked from the flag spin-loop.
     6cuda::atomic<int, cuda::thread_scope_system> flag = 0;
     7__global__ void producer() { flag.store(1); }
     8int main() {
     9    cudaHostRegister(&flag, sizeof(flag));
    10    producer<<<1,1>>>();
    11    while (flag.load() == 0);
    12    return cudaDeviceSynchronize();
    13}
     1// Example: Execution.Model.API.3
     2// Allowed outcome: eventually, no thread makes progress.
     3// Rationale: same as Example.Model.API.2, with the addition that a single CUDA query API call does not guarantee
     4// the device thread eventually starts, only repeated CUDA query API calls do (see Execution.Model.API.4).
     5cuda::atomic<int, cuda::thread_scope_system> flag = 0;
     6__global__ void producer() { flag.store(1); }
     7int main() {
     8    cudaHostRegister(&flag, sizeof(flag));
     9    producer<<<1,1>>>();
    10    (void)cudaStreamQuery(0);
    11    while (flag.load() == 0);
    12    return cudaDeviceSynchronize();
    13}
     1// Example: Execution.Model.API.4
     2// Outcome: terminates.
     3// Rationale: same as Example.Model.API.3, but this example repeatedly calls
     4// a CUDA query API in within the flag spin-loop, which guarantees that the device thread
     5// eventually makes progress.
     6cuda::atomic<int, cuda::thread_scope_system> flag = 0;
     7__global__ void producer() { flag.store(1); }
     8int main() {
     9    cudaHostRegister(&flag, sizeof(flag));
    10    producer<<<1,1>>>();
    11    while (flag.load() == 0) {
    12        (void)cudaStreamQuery(0);
    13    }
    14    return cudaDeviceSynchronize();
    15}

5.8.3.1 Dependencies(依赖)

device 线程只有在其所有依赖完成后才能启动。

**注:**阻止 device 线程开始取得进展的依赖可以通过CUDA Stream Commands创建。例如,这些依赖可能要求完成CUDA Events或CUDA Kernels等操作。

由于依赖产生的 CUDA API 前进进度保证示例
     1// Example: Execution.Model.Stream.0
     2// Allowed outcome: eventually, no thread makes progress.
     3// Rationale: while CUDA guarantees that one device thread makes progress, since there
     4// is no dependency between `first` and `second`, it does not guarantee which thread,
     5// and therefore it could always pick the device thread from `second`, which then never
     6// unblocks from the spin-loop.
     7// That is, `second` may starve `first`.
     8cuda::atomic<int, cuda::thread_scope_system> flag = 0;
     9__global__ void first() { flag.store(1, cuda::memory_order_relaxed); }
    10__global__ void second() { while(flag.load(cuda::memory_order_relaxed) == 0) {} }
    11int main() {
    12    cudaHostRegister(&flag, sizeof(flag));
    13    cudaStream_t s0, s1;
    14    cudaStreamCreate(&s0);
    15    cudaStreamCreate(&s1);
    16    first<<<1,1,0,s0>>>();
    17    second<<<1,1,0,s1>>>();
    18    return cudaDeviceSynchronize();
    19}
     1// Example: Execution.Model.Stream.1
     2// Outcome: terminates.
     3// Rationale: same as Execution.Model.Stream.0, but this example has a stream dependency
     4// between first and second, which requires CUDA to run the grids in order.
     5cuda::atomic<int, cuda::thread_scope_system> flag = 0;
     6__global__ void first() { flag.store(1, cuda::memory_order_relaxed); }
     7__global__ void second() { while(flag.load(cuda::memory_order_relaxed) == 0) {} }
     8int main() {
     9    cudaHostRegister(&flag, sizeof(flag));
    10    cudaStream_t s0;
    11    cudaStreamCreate(&s0);
    12    first<<<1,1,0,s0>>>();
    13    second<<<1,1,0,s0>>>();
    14    return cudaDeviceSynchronize();
    15}
Prev
5.7 CUDA C++ 内存模型(CUDA C++ Memory Model)