速查表
写 kernel 时最常回头查的东西,集中放在一页。想理解背后的原因,回到对应的课程。
内置变量与限定符
| 写法 | 含义 |
|---|---|
threadIdx.x/y/z | block 内的线程坐标 |
blockIdx.x/y/z | grid 内的 block 坐标 |
blockDim.x/y/z | 每个 block 的线程数 |
gridDim.x/y/z | grid 的 block 数 |
warpSize | 恒为 32 |
__global__ | kernel 入口,GPU 执行、host 调用,返回 void |
__device__ | GPU 执行、GPU 调用 |
__host__ __device__ | 两边各编译一份 |
__shared__ | block 内共享的片上内存 |
__constant__ | 只读常量内存,64 KB,广播优化 |
__restrict__ | 无别名保证,配合 const 可走只读缓存 |
__launch_bounds__(T, B) | 约束编译器的寄存器预算 |
索引推导
// 一维int i = blockIdx.x * blockDim.x + threadIdx.x;if (i < n) { ... } // 二维(x 对应列,即内存连续方向)int col = blockIdx.x * blockDim.x + threadIdx.x;int row = blockIdx.y * blockDim.y + threadIdx.y;if (row < rows && col < cols) m[row * cols + col] = ...; // grid-stride loop:grid 尺寸与数据规模解耦for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; i += gridDim.x * blockDim.x) { ... } // block 内线性 tid(warp 就是按它每 32 个切一组)int tid = threadIdx.x + threadIdx.y * blockDim.x + threadIdx.z * blockDim.x * blockDim.y; // 向上取整的 grid 尺寸int blocks = (n + threads - 1) / threads;Runtime API
cudaMalloc(&d_p, bytes); // 注意是 &d_pcudaMallocManaged(&p, bytes); // 统一内存cudaHostAlloc(&h_p, bytes, cudaHostAllocDefault); // pinned,异步拷贝的前提cudaMemcpy(dst, src, bytes, cudaMemcpyHostToDevice);cudaMemcpyAsync(dst, src, bytes, kind, stream);cudaMemset(d_p, 0, bytes);cudaFree(d_p); cudaFreeHost(h_p); cudaStreamCreate(&s);cudaStreamSynchronize(s); // 只等这条流cudaStreamWaitEvent(s2, evt, 0); // 跨流依赖,CPU 不阻塞cudaDeviceSynchronize(); // 等一切,仅用于调试和退出前 cudaEventCreate(&e); cudaEventRecord(e, s);cudaEventSynchronize(e);cudaEventElapsedTime(&ms, start, stop); // 唯一正确的 kernel 计时方式 cudaGetLastError(); // launch 时的同步错误cudaGetErrorString(err);cudaOccupancyMaxPotentialBlockSize(&minGrid, &block, kernel, 0, 0);cudaFuncSetAttribute(kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, 96*1024);同步与 warp 原语
| 原语 | 作用域 | 说明 |
|---|---|---|
__syncthreads() | block | 屏障,绝不能放在分叉的分支里 |
__syncwarp(mask) | warp | Volta 之后 warp 内交换数据必须显式同步 |
__threadfence() | device | 内存序,不是屏障 |
__shfl_sync(m, v, src) | warp | 广播 |
__shfl_down_sync(m, v, d) | warp | 归约,结果落在 lane 0 |
__shfl_xor_sync(m, v, k) | warp | 蝶形,所有 lane 都拿到结果 |
__ballot_sync(m, pred) | warp | 投票位图,配合 __popc 做流压缩 |
__activemask() | warp | 分叉路径内构造掩码 |
CUDA C++
__inline__ __device__ float warpReduceSum(float v) { for (int off = 16; off > 0; off >>= 1) v += __shfl_down_sync(0xffffffff, v, off); return v;}编译选项
| 选项 | 作用 |
|---|---|
-arch=sm_80 | 同时指定虚拟架构与真实架构 |
-gencode arch=compute_80,code=sm_80 | 精确控制单个目标 |
-gencode arch=compute_90,code=compute_90 | 保留 PTX,新卡可 JIT,发布时务必带上 |
-Xptxas -v | 打印寄存器用量与溢出情况 |
-lineinfo | 加行号供 profiler 与 sanitizer 定位,不影响优化 |
-G | device 端调试,会关闭所有优化,慎用 |
--use_fast_math | 快速数学,牺牲精度换速度 |
-maxrregcount=N | 全局限制寄存器,粒度太粗,优先用 __launch_bounds__ |
--default-stream per-thread | 让默认流不再与其他流隐式同步 |
| 架构 | 代号 | 代表型号 |
|---|---|---|
sm_70 | Volta | V100 |
sm_75 | Turing | T4、RTX 20 系 |
sm_80 | Ampere | A100 |
sm_86 | Ampere | RTX 30 系、A10 |
sm_89 | Ada | RTX 40 系、L4 |
sm_90 | Hopper | H100 |
Profiling 命令
# 第一步:看整体时间线,找出真正的瓶颈 kernelnsys profile -o report --stats=true ./app # 第二步:深挖单个 kernelncu --set full -o profile ./appncu --kernel-name myKernel --launch-count 3 ./app # 只看关键指标(快很多)ncu --metrics \ sm__throughput.avg.pct_of_peak_sustained_elapsed,\ gpu__dram_throughput.avg.pct_of_peak_sustained_elapsed,\ sm__warps_active.avg.pct_of_peak_sustained_active \ ./app # 检查访存合并:sectors/requests 理想为 4,接近 32 说明完全没合并ncu --metrics \ l1tex__t_sectors_pipe_lsu_mem_global_op_ld.sum,\ l1tex__t_requests_pipe_lsu_mem_global_op_ld.sum ./app # 检查 bank conflictncu --metrics l1tex__data_bank_conflicts_pipe_lsu_mem_shared.sum ./app # 正确性compute-sanitizer --tool memcheck ./appcompute-sanitizer --tool racecheck ./app # 看编译产物cuobjdump -sass ./app | lessnvcc -ptx -arch=sm_80 -o k.ptx k.cu经验数值
这些量级值得记住,能帮你快速判断一个想法值不值得试
| 项目 | 量级 |
|---|---|
| 寄存器延迟 | ~1 周期 |
| 共享内存延迟 | ~20-30 周期 |
| L2 延迟 | ~200 周期 |
| 全局内存延迟 | ~400-800 周期 |
| sector 粒度 | 32 字节 |
| 共享内存 bank | 32 个,每个 4 字节宽 |
| A100 峰值带宽 | 1.55 TB/s |
| A100 FP32 算力 | 19.5 TFLOPS |
| A100 BF16 Tensor Core | 312 TFLOPS(约 16 倍) |
| A100 计算强度拐点 | ≈ 12.5 FLOP/Byte |
| kernel launch 开销 | 3-10 微秒 |
| 一个 block 最大线程数 | 1024 |
排错对照
| 症状 | 最可能的原因 |
|---|---|
kernel 里的 printf 没输出 | 缺少同步点,进程已退出 |
| 结果全是 0 | 忘了 cudaMemcpy 回来,或 launch 失败但没检查错误 |
| 结果偶发错误 | 缺 __syncthreads();或在 PyTorch 扩展里用了默认流 |
invalid configuration argument | blockDim 超过 1024,或共享内存超限 |
no kernel image is available | 编译的 -arch 与实际 GPU 不匹配,且没保留 PTX |
| 拷贝与计算不重叠 | host 内存不是 pinned,或用了默认流 |
| 改了一行代码性能暴跌 | 寄存器数跨过台阶导致占用率下降,查 -Xptxas -v |
| 性能远低于带宽上限 | 访存没合并,查 sectors/requests 比值 |
| 共享内存 kernel 很慢 | bank conflict,试试行宽 +1 |
还没开始学?从 第一个 kernel 开始,或者直接去 occupancy 计算器 看看是什么在卡你的 kernel。