先记住一句话
kernel launch 通常对 host 异步;“launch 没报错”不等于 kernel 已完成或结果正确,必须在正确边界检查 launch error 与 execution error。
1. 最小 vector-add kernel
__global__ void add(const float* a, const float* b, float* c, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) c[i] = a[i] + b[i];
}
int threads = 256;
int blocks = (n + threads - 1) / threads;
add<<<blocks, threads>>>(a, b, c, n);__global__ 表示 host launch/device execute;grid 覆盖 n elements,最后 block 可能部分越界,所以 guard 必不可少。
2. 一次完整生命周期
- 选 device/context;
- 分配 host/device memory;
- 初始化并 H2D copy;
- 配置 grid/block/shared memory/stream;
- launch kernel;
- 检查 launch + asynchronous execution errors;
- 必要时同步并 D2H copy;
- 释放资源。
framework 隐藏了多数步骤,但 custom op 仍必须服从当前 device、stream、allocator 和 tensor lifetime。
3. Runtime API 与 Driver API
Runtime API(cuda*)更高层,自动管理 primary context/编译链接;Driver API(cu*)显式 module/context/function,适合 JIT、runtime loading 与更细控制。两者能互操作,但 context ownership/version 边界要清楚。
4. Memory choices
| 方式 | 用途与边界 |
|---|---|
cudaMalloc | device allocation;同步 allocator 可有 overhead |
cudaMallocAsync | stream-ordered pool allocation,便于复用 |
| Pinned host memory | 支持真正 async DMA;过量会伤系统 |
| Unified Memory | 统一地址与按需迁移;方便但 page fault/locality 要管理 |
| Mapped/zero-copy | GPU 直接访问 host memory;离散 GPU latency/bandwidth 有限 |
5. Synchronization scopes
__syncthreads():block 内 barrier + shared/global visibility 规则;- warp primitives:shuffle/ballot 与显式 active mask;
- stream event:跨 streams 建 dependency;
cudaStreamSynchronize:等待单 stream;cudaDeviceSynchronize:粗粒度等待整个 device,调试方便、性能代价大。
6. Error handling
kernel<<<...>>>(...);
check(cudaGetLastError()); // launch/config error
check(cudaStreamSynchronize(s)); // surfaces async execution error at boundaryillegal memory access 可能在后续无关 API 才显现,且 CUDA errors 常具有 sticky/context-corrupting 特性;production 不能假设 catch exception 后继续安全复用同一进程。
7. Grid-stride loop
for (int i = blockIdx.x*blockDim.x + threadIdx.x;
i < n;
i += blockDim.x*gridDim.x) { ... }它让有限 threads 覆盖任意 n,便于限制 grid、复用 threads 和调试;仍需保证 index type 足够大、访问 pattern 合并。
8. 编译链
nvcc 分离 host/device code,device side 可生成 architecture-specific cubin/SASS 和/或 virtual PTX。PTX 不是最终硬件指令;driver JIT 兼容性、目标 sm_xx、fat binary 与 toolkit/driver version 决定部署。
9. 最小 correctness checklist
- 所有 shapes、strides、dtype、device、alignment 与 bounds;
- 零长度、非整除、极大 index、non-contiguous tensors;
- 同 stream ordering 与跨 stream event;
- CPU/reference 多随机 shape 对比;
- compute-sanitizer、racecheck 与 deterministic seed;
- release build 仍保留 error recorder。
10. 四个 CUDA lifecycle 手算
例 1:global index
blockIdx.x=3, blockDim.x=256, threadIdx.x=17,global index=3×256+17=785。若 N=780,该 thread 必须被 if(i<N) 挡住。
例 2:allocation 与 copy 大小
10 million FP32 elements 占 10,000,000×4=40 MB(十进制)。A/B/C 三 arrays device memory 约 120 MB;一次 H2D 传 A+B 是 80 MB。
例 3:grid-stride loop
Launch 80 blocks×256=20,480 threads 处理 N=1,000,000。每 thread 平均处理约 1,000,000/20,480=48.8 elements,indices 依次 i、i+20480…。
例 4:同步暴露错误
Host 在 5 μs 内 enqueue 一个运行 2 ms 的 kernel;launch check 可能仍成功。只有随后 event/device sync 等到约 2 ms 后,越界 execution error 才能被可靠报告。
cudaMemcpy 后能读到结果,不代表前面的 async 逻辑正确;某些同步 API 恰好掩盖 race。优化去掉同步后 bug 才暴露,是原程序依赖了未声明顺序。
自测
1. 为什么 kernel launch 后立即计 CPU 时间不对?
launch 对 host 通常异步,计到的是 enqueue 时间;需 CUDA events 或同步边界测 device execution。
2. __syncthreads 能放在部分 threads 才进入的分支吗?
只有当整个 block 对该条件一致时安全;否则部分 threads 不到 barrier 会死锁/未定义。
3. PTX 等于 GPU machine code 吗?
不等于;PTX 是 virtual ISA,最终由工具链/driver 生成目标 architecture 的 SASS。
官方资料
NVIDIA 最新 CUDA Programming Guide覆盖 CUDA C++/Python、SIMT、tile kernels、async execution、memory model 与 advanced APIs。