Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 系统与高性能 / Systems & Performance

计算系统 / Computing Systems

1. 计算系统:计算机如何执行与加速程序

2. 从 C++ 源码到 CPU 执行

3. CPU 流水线、乱序执行与分支预测

4. Cache、一致性、伪共享与 NUMA

5. GPU、SM、Warp 与显存

6. 计算执行模型:程序怎样映射到机器

7. SIMD 与编译器向量化

8. C++ 多线程与 OpenMP

9. CUDA 平台与编程模型

10. CUDA Kernel、内存与性能

11. CPU-GPU 异构流水线

12. MPI 与分布式并行

13. 并行算法模式

14. 性能模型与工具

15. 递进学习项目:从单线程到集群

历史完整正文 / Original Deep Dives

1. 历史完整正文:统一前文章逐篇保留

原体系结构与硬件 / Original Architecture

1. 硬件编程与高性能计算:一张可走通的学习地图 / A Practical Learning Map for Hardware Programming and HPC

2. 计算机体系结构:CPU、内存与 GPU / Computer Architecture: CPUs, Memory, and GPUs

3. 计算机架构基础——为什么 GPU 比 CPU 更快 / Computer Architecture Fundamentals: Why GPUs Outperform CPUs

4. 并行计算理论——30 天训练能优化到多快? / Parallel Computing Theory and the Limits of Training Acceleration

5. GPU 架构深入——上万个核心如何分工协作 / GPU Architecture and Massive Parallel Execution

6. CUDA 编程模型——把矩阵乘法映射到 GPU / The CUDA Programming Model for Mapping Matrix Multiplication to GPUs

7. CUDA 内存管理——百亿参数如何装进显存 / CUDA Memory Management for Large Models

8. CUDA 性能优化——从 30 天缩短到 10 天 / CUDA Performance Optimization

9. CPU 并行编程——OpenMP 与 SIMD 向量化 / CPU Parallel Programming with OpenMP and SIMD

10. HPC 集群与 MPI——多节点分布式训练 / HPC Clusters and MPI for Distributed Training

11. 异构计算——CPU 与 GPU 如何协同工作 / Heterogeneous Computing with CPUs and GPUs

12. 深度学习训练优化实战——从 30 天到 3 天 / Deep Learning Training Optimization from Thirty Days to Three

13. 性能分析工具——找到真正的瓶颈 / Performance Analysis Tools for Finding Real Bottlenecks

14. NPU 全景——昇腾/寒武纪/TPU/苹果生态 / The NPU Landscape: Ascend, Cambricon, TPU, and Apple

15. 未来趋势——2030 年的计算机会是什么形态 / Future Computing Trends Toward 2030

16. 硬件与高性能计算:从“程序为什么慢”开始 / Hardware and HPC Starting from Why Programs Are Slow

原并行计算 / Original Parallel Computing

1. 并行计算:从 SIMD 到 MPI / Parallel Computing from SIMD to MPI

2. 并行计算全景:从晶体管、CPU、GPU 到计算集群 / Parallel Computing from Transistors, CPUs, and GPUs to Clusters

3. 并行计算基础:任务分解、加速比与可扩展性 / Parallel Computing Fundamentals: Decomposition, Speedup, and Scalability

4. 处理器体系结构:从指令流水线到多核芯片 / Processor Architecture from Instruction Pipelines to Multicore Chips

5. CPU 并行:多线程、SIMD、Cache 一致性与 NUMA / CPU Parallelism with Threads, SIMD, Cache Coherence, and NUMA

6. 内存层次:Cache、带宽、局部性与一致性 / Memory Hierarchies, Bandwidth, Locality, and Coherence

7. GPU 体系结构:SIMT、Warp、SM 与吞吐优先设计 / GPU Architecture with SIMT, Warps, and Streaming Multiprocessors

8. CUDA 编程模型:Thread、Block、Grid 与内存协作 / CUDA Threads, Blocks, Grids, and Cooperative Memory Access

9. 并行算法模式:Map、Reduce、Scan、Stencil 与任务图 / Parallel Patterns: Map, Reduce, Scan, Stencil, and Task Graphs

10. 异构计算:CPU、GPU、NPU 如何协同工作 / Heterogeneous Computing with CPUs, GPUs, and NPUs

11. 分布式并行:MPI、集合通信、RDMA 与多机多卡 / Distributed Parallelism with MPI, Collective Communication, and RDMA

12. 性能工程:测量、Roofline、瓶颈定位与优化闭环 / Performance Engineering with Measurement, Roofline, and Bottleneck Analysis

13. 并行计算实战:AI、CAE、图像与科学计算 / Parallel Computing for AI, CAE, Imaging, and Scientific Computing

14. 并行计算实践路线:从单核优化到多机多卡 / A Parallel Computing Project Path from Single-Core to Multi-Node GPUs

本页目录

CUDA 平台与编程模型 ​

GPU 是硬件,CUDA 不是 GPU 的别名。CUDA 是 NVIDIA 提供的编程平台:包含编程模型、编译工具、驱动接口、运行时、调试分析工具和加速库。

1. CUDA 软件栈 ​

text
application
├── CUDA libraries: cuBLAS / cuFFT / ...
├── CUDA Runtime API: cudaMalloc, cudaMemcpy, kernel launch
├── CUDA Driver API: contexts, modules, lower-level control
├── NVIDIA driver
└── NVIDIA GPU hardware

CUDA Toolkit: nvcc + headers + libraries + tools
1
2
3
4
5
6
7
8
  • Driver 负责操作系统与 GPU 的底层交互;
  • Runtime API 提供更方便的 C/C++ 接口,通常在内部使用驱动能力;
  • Toolkit 是开发工具集合,不等于显卡驱动;
  • nvcc 协调 host C++ 与 device code 的编译;
  • PTX 是面向 NVIDIA GPU 的虚拟指令表示,可由驱动进一步生成目标机器码;
  • cuBLAS 等库 是经高度优化的现成算法,通常优先于自己重写通用算子。

安装 Toolkit 后能编译,不代表机器一定有可运行 CUDA 的 GPU;只有驱动也不代表拥有完整开发工具链。

2. Host 与 Device ​

text
CPU / host                         GPU / device
allocate and prepare data          device memory
        | cudaMemcpy H2D ------------> |
        | launch kernel -------------> | many threads execute
        | cudaMemcpy D2H <-------------|
consume result
1
2
3
4
5
6

Kernel 启动对 Host 通常是异步的;一次同步的数据拷贝或显式同步可能让 Host 等待。准确行为要看 API、内存类型与 Stream。

3. 最小 Kernel ​

cpp
__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);
1
2
3
4
5
6
7
8

边界判断不可省略,因为向上取整后的逻辑线程数可能大于 n。

4. 编程模型映射到硬件 ​

text
CUDA programming model                 NVIDIA hardware

Grid
├── Block 0 -------------------------> resident on one SM
│   ├── Thread 0                       ├── Warp scheduler
│   ├── ...                            ├── registers
│   └── Thread 255                     └── shared memory + units
└── Block 1 -------------------------> an available SM

threads in a block -- groups of usually 32 --> warps
1
2
3
4
5
6
7
8
9
10

一个 Block 在一次驻留期间位于一个 SM;一个 SM 可同时驻留多个 Block;不同 Block 的先后顺序通常不能假设。Block 是协作和同步边界,Warp 是硬件执行组织,二者不是同一层概念。

5. 同步边界 ​

__syncthreads() 是 Block 内 barrier,并让同一 Block 的相关共享内存操作按规则可见。它不是全 Grid barrier。在条件分支中让部分线程到达 barrier、部分不到达,会造成错误。

跨 Block 的全局阶段常拆成多个 Kernel,因为前一个 Kernel 完成可作为下一阶段边界;更高级的协作机制有额外前提,不能默认使用。

深入原理与工程实践 ​

前面的内容负责建立统一心智模型;下面把同一主题继续拆到执行过程、代码、性能代价与工程判断。

<!-- migrated-deep-dive:start -->

完整迁入:原 CUDA 编程模型全文 ​

CUDA 编程模型——把矩阵乘法映射到 GPU / The CUDA Programming Model for Mapping Matrix Multiplication to GPUs ​

📅 创建时间:2026-06-02 🏷️ 标签:#CUDA #kernel #grid #block #thread #nvcc 📚 前置知识:[[03-gpu-architecture]](GPU 架构) 📚 相关知识:[[05-cuda-kernel-and-memory]](CUDA 内存) [[06-cuda-optimization]](性能优化)


先抓住直觉 ​

CUDA 的核心不是新语法,而是把一份工作拆给许多编号不同的线程。Host 负责准备和发号施令,Device 负责批量计算;每个线程根据自己的编号找到要处理的数据。

  • 必须理解:Host/Device 分工;Grid、Block、Thread 的包含关系;线程索引如何映射到数据。
  • 用到再查:函数修饰符全集、CMake 配置和错误码。
  • 最小目标:先看懂向量加法,再进入矩阵乘法;不要一开始就手写高性能 GEMM。

场景:第一个 CUDA 程序——向量加法 ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  你已经理解了 GPU 的架构:                                  │
│  - 6912 个 CUDA 核心                                       │
│  - SM → Warp → Thread 的调度层次                          │
│  - SIMT 执行模型                                          │
│                                                             │
│  现在,你要把这个简单的 C++ 代码改成 CUDA 版本:            │
│                                                             │
│  // C++ 版本                                              │
│  for (int i = 0; i < N; i++) {                           │
│      c[i] = a[i] + b[i];                                  │
│  }                                                        │
│                                                             │
│  目标:在 GPU 上并行执行这段计算                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

第1节:Host 和 Device——CUDA 的核心概念 ​

两种代码,两种运行环境 ​
┌─────────────────────────────────────────────────────────────┐
│                    CUDA 程序的两部分                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Host(C++ 代码,运行在 CPU 上):                          │
│    - 数据准备:把数据从 CPU 内存拷贝到 GPU 显存             │
│    - 调用 kernel:告诉 GPU 要执行什么计算                    │
│    - 结果回收:把结果从 GPU 显存拷回 CPU 内存                │
│    - 控制流:for/if 等逻辑                                  │
│                                                             │
│  Device(CUDA 代码,运行在 GPU 上):                       │
│    - 具体计算:每个线程执行一小部分计算                      │
│    - 线程协作:同 SM 内线程共享数据                        │
│                                                             │
│  形象理解:                                               │
│  Host = 包工头(CPU):分配任务、准备材料、收集结果         │
│  Device = 建筑队(GPU):大规模并行施工                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
代码分布示例 ​
cpp
// ================================================================
// main.cpp(C++,Host 端)
// ================================================================
#include <cuda_runtime.h>  // CUDA 运行时 API
#include <stdio.h>

int main() {
    const int N = 1000000;
    float *h_a, *h_b, *h_c;       // h_ = host 内存(CPU)
    float *d_a, *d_b, *d_c;        // d_ = device 内存(GPU)

    // Step 1: 分配 Host 内存
    h_a = (float*)malloc(N * sizeof(float));
    h_b = (float*)malloc(N * sizeof(float));
    h_c = (float*)malloc(N * sizeof(float));

    // Step 2: 初始化数据
    for (int i = 0; i < N; i++) {
        h_a[i] = i * 1.0f;
        h_b[i] = i * 2.0f;
    }

    // Step 3: 分配 Device 内存(GPU 显存)
    cudaMalloc(&d_a, N * sizeof(float));
    cudaMalloc(&d_b, N * sizeof(float));
    cudaMalloc(&d_c, N * sizeof(float));

    // Step 4: CPU 数据拷贝到 GPU
    cudaMemcpy(d_a, h_a, N * sizeof(float), cudaMemcpyHostToDevice);
    cudaMemcpy(d_b, h_b, N * sizeof(float), cudaMemcpyHostToDevice);

    // Step 5: 调用 kernel(启动 GPU 计算)
    // 解释见下一节
    add<<<blocks, threads>>>(d_a, d_b, d_c, N);

    // Step 6: GPU 结果拷贝回 CPU
    cudaMemcpy(h_c, d_c, N * sizeof(float), cudaMemcpyDeviceToHost);

    // Step 7: 验证结果
    for (int i = 0; i < 10; i++) {
        printf("c[%d] = %f\n", i, h_c[i]);
    }

    // Step 8: 释放内存
    free(h_a); free(h_b); free(h_c);
    cudaFree(d_a); cudaFree(d_b); cudaFree(d_c);

    return 0;
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
cpp
// ================================================================
// vector_add.cuh(CUDA 头文件)
// ================================================================
#ifndef VECTOR_ADD_CUH
#define VECTOR_ADD_CUH

// __global__ 声明的函数从 CPU 调用,在 GPU 上执行
void add(float* a, float* b, float* c, int N);

#endif
1
2
3
4
5
6
7
8
9
10
cpp
// ================================================================
// vector_add.cu(CUDA 源代码,Device 端)
// ================================================================

// CUDA 头文件
#include "vector_add.cuh"
#include <cuda_runtime.h>

// __global__: 从 CPU 调用,在 GPU 上运行的函数(称为 "kernel")
// 这是 CUDA 编程的核心:一个 kernel 被 N 个线程并行执行
__global__
void add_kernel(float* a, float* b, float* c, int N) {
    // 每个线程计算一个元素的结果
    int i = blockIdx.x * blockDim.x + threadIdx.x;

    // 边界检查:防止数组越界
    if (i < N) {
        c[i] = a[i] + b[i];
    }
}

// 包装函数(Host 端调用这个,它再调用 kernel)
void add(float* a, float* b, float* c, int N) {
    // 计算 grid 和 block 的维度
    int threadsPerBlock = 256;
    int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;

    // 启动 kernel:<<<blocksPerGrid, threadsPerBlock>>>
    add_kernel<<<blocksPerGrid, threadsPerBlock>>>(a, b, c, N);

    // 注意:kernel 启动是异步的!
    // CPU 不会等待 GPU 完成就继续执行下一行
    // 下一行立即执行,除非调用 cudaMemcpy 或 cudaDeviceSynchronize
}
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
编译和运行 ​
bash
### 编译
nvcc vector_add.cu main.cpp -o vector_add

### 运行(需要机器上有 NVIDIA GPU)
./vector_add

### 如果没有 GPU,可以模拟运行(需要 NVIDIA 开发驱动)
### CUDA Toolkit 自带 cuda-gdb 和 compute-sanitizer
1
2
3
4
5
6
7
8

第2节:Grid / Block / Thread——CUDA 的三层线程组织 ​

线程层次结构 ​
┌─────────────────────────────────────────────────────────────┐
│                    CUDA 线程层次结构                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Grid(整个 GPU)                                           │
│  ┌───────────────────────────────────────────────────────┐ │
│  │                                                       │ │
│  │   Block 0    Block 1    Block 2    Block 3   ...    │ │
│  │   ┌─────┐    ┌─────┐    ┌─────┐    ┌─────┐         │ │
│  │   │  T0  │    │  T0  │    │  T0  │    │  T0  │         │ │
│  │   │  T1  │    │  T1  │    │  T1  │    │  T1  │         │ │
│  │   │  ... │    │  ... │    │  ... │    │  ... │         │ │
│  │   │ T255 │    │ T255 │    │ T255 │    │ T255 │         │ │
│  │   └─────┘    └─────┘    └─────┘    └─────┘         │ │
│  │   256线程    256线程    256线程    256线程            │ │
│  │                                                       │ │
│  └───────────────────────────────────────────────────────┘ │
│                                                             │
│  Grid 的维度:gridDim.x, gridDim.y, gridDim.z            │
│  Block 的维度:blockDim.x, blockDim.y, blockDim.z       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
线程索引计算 ​
cpp
// kernel 内部,每个线程通过内置变量知道自己负责哪个数据

// 内置变量速查:
//   threadIdx.x/y/z    当前线程在线程块内的索引
//   blockIdx.x/y/z     当前线程块在 grid 内的索引
//   blockDim.x/y/z     每个线程块的维度
//   gridDim.x/y/z      grid 的维度

// 计算全局线程索引(唯一标识每个线程)
int i = blockIdx.x * blockDim.x + threadIdx.x;

// 二维 Grid + 二维 Block 的例子:
// gridDim = (Gx, Gy), blockDim = (Bx, By)
// 全局索引 = (by * Bx + bx) * (Gy * Gx) + (ty * Bx + tx)
// 但实际代码中通常只用一维就够用了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
维度设置规则 ​
┌─────────────────────────────────────────────────────────────┐
│                    维度设置规则                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Block 维度(每个 block 的线程数):                        │
│  - 最大值:1024(硬件限制)                                │
│  - 常用值:128, 256, 512, 1024                           │
│  - 推荐值:256(经验最优)                                 │
│                                                             │
│  Block 内线程数最好是 32 的倍数(Warp 大小)              │
│  因为 warp = 32 threads,必须整 warp 调度                  │
│  如果不是 32 的倍数,会浪费部分硬件                        │
│                                                             │
│  Grid 维度(block 的数量):                               │
│  - 最大值:2^31 - 1(x方向)、65535(y)、65535(z)      │
│  - Grid 大小 = block 数 × 每 block 线程数 = 总线程数       │
│  - 总线程数应该 >= GPU 核心数(通常远大于)                │
│                                                             │
│  示例:                                                    │
│  A100 有 6912 个核心,N = 1000000                         │
│  threadsPerBlock = 256                                    │
│  blocksPerGrid = ceil(1000000 / 256) = 3907              │
│  总线程数 = 3907 × 256 = 1,000,192 > 6912 ✅             │
│                                                             │
└─────────────────────────────────────────────────────────────┘
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

第3节:CUDA 关键字——三种函数的区别 ​

__global__ / __device__ / __host__ ​
┌─────────────────────────────────────────────────────────────┐
│                    CUDA 函数修饰符                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  __global__ void my_kernel(...)                           │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  调用来源:CPU(Host)                                │ │
│  │  执行位置:GPU(Device)                              │ │
│  │  用途:kernel 入口(必须用这个声明)                 │ │
│  │  注意:返回类型必须是 void                           │ │
│  └───────────────────────────────────────────────────────┘ │
│                                                             │
│  __device__ float my_device_func(float x)                 │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  调用来源:GPU(Device)                             │ │
│  │  执行位置:GPU(Device)                             │ │
│  │  用途:GPU 内部调用的辅助函数                        │ │
│  │  注意:只能被 __global__ 或 __device__ 函数调用      │ │
│  └───────────────────────────────────────────────────────┘ │
│                                                             │
│  __host__ void my_host_func(...)  ← 默认,省略不写       │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  调用来源:CPU(Host)                               │ │
│  │  执行位置:CPU(Host)                               │ │
│  │  用途:普通 C++ 函数                                  │ │
│  │  注意:可以同时有 __host__ 和 __device__(两边都编译)│ │
│  └───────────────────────────────────────────────────────┘ │
│                                                             │
└─────────────────────────────────────────────────────────────┘
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
变量修饰符 ​
┌─────────────────────────────────────────────────────────────┐
│                    CUDA 变量修饰符                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  __device__                        ← 默认,全局内存,GPU 上 │
│  - 全局内存变量,所有线程可见                                │
│  - 通过 cudaMalloc/cudaFree 管理                          │
│                                                             │
│  __shared__                        ← 共享内存,同 SM 内线程 │
│  - 声明在 kernel 内部(或者用 shared 限定符)             │
│  - 同一 block 内所有线程共享                               │
│  - 速度极快(~1ns,接近寄存器)                           │
│                                                             │
│  __constant__                      ← 常量内存,只读         │
│  - 所有线程只读                                            │
│  - 编译时常量或运行时通过 cudaMemcpyToSymbol 设置         │
│  - 有硬件缓存(广播优化)                                   │
│                                                             │
│  __managed__                       ← 统一内存(CUDA 6.0)   │
│  - CPU 和 GPU 共享同一地址空间                             │
│  - 自动在需要时迁移数据                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

第4节:矩阵乘法的 CUDA 实现——从向量到矩阵 ​

最简单的矩阵乘法 ​
目标:C = A × B
  A: M×K 矩阵
  B: K×N 矩阵
  C: M×N 矩阵
1
2
3
4
cpp
// matrix_mul.cu
__global__
void matrix_mul_kernel(float* A, float* B, float* C,
                      int M, int N, int K) {
    // 每个线程计算 C 的一个元素 C[row][col]
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;

    if (row < M && col < N) {
        float sum = 0.0f;
        for (int k = 0; k < K; k++) {
            sum += A[row * K + k] * B[k * N + col];
        }
        C[row * N + col] = sum;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cpp
// 调用方式(二维 Grid + 二维 Block)
void matrix_mul(float* A, float* B, float* C,
                int M, int N, int K) {
    // Block 维度:16×16 = 256 线程/block
    dim3 threadsPerBlock(16, 16);

    // Grid 维度:ceil(M/16) × ceil(N/16)
    dim3 blocksPerGrid(
        (N + threadsPerBlock.x - 1) / threadsPerBlock.x,
        (M + threadsPerBlock.y - 1) / threadsPerBlock.y
    );

    matrix_mul_kernel<<<blocksPerGrid, threadsPerBlock>>>(
        A, B, C, M, N, K);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
为什么一个线程算一个输出元素? ​
┌─────────────────────────────────────────────────────────────┐
│                线程与输出的映射                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  C 是 M×N 的矩阵,有 M×N 个输出元素                       │
│  每个输出元素 C[i][j] 的计算是独立的!                     │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐  │
│  │  Thread (block=0, thread=0) → 计算 C[0][0]        │  │
│  │  Thread (block=0, thread=1) → 计算 C[0][1]        │  │
│  │  Thread (block=0, thread=2) → 计算 C[0][2]        │  │
│  │  ...                                                │  │
│  │  Thread (block=1, thread=0) → 计算 C[1][0]        │  │
│  │  ...                                                │  │
│  │  Thread (block=M-1, thread=N-1) → 计算 C[M-1][N-1]│  │
│  └─────────────────────────────────────────────────────┘  │
│                                                             │
│  M×N 个线程同时执行 = M×N 个输出同时计算 = 完美并行!       │
│                                                             │
│  这就是 GPU 编程的核心思想:                               │
│  把计算拆分成 N 个独立的小任务,每个线程做一个               │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
更高效的共享内存版本(预告) ​
cpp
// 使用共享内存优化(大幅减少全局内存访问)
// 具体原理在第 5 章讲解,这里先看结构
__global__
void matrix_mul_shared_kernel(float* A, float* B, float* C,
                              int M, int N, int K) {
    // 共享内存:同 block 内线程共享
    __shared__ float As[BLOCK_SIZE][BLOCK_SIZE];
    __shared__ float Bs[BLOCK_SIZE][BLOCK_SIZE];

    int row = blockIdx.y * BLOCK_SIZE + threadIdx.y;
    int col = blockIdx.x * BLOCK_SIZE + threadIdx.x;
    float sum = 0.0f;

    // 分块计算:每次加载一块到共享内存
    for (int m = 0; m < (K + BLOCK_SIZE - 1) / BLOCK_SIZE; m++) {
        // 线程协作加载到共享内存
        if (row < M && (m * BLOCK_SIZE + threadIdx.x) < K)
            As[threadIdx.y][threadIdx.x] = A[row * K + m * BLOCK_SIZE + threadIdx.x];
        if (col < N && (m * BLOCK_SIZE + threadIdx.y) < K)
            Bs[threadIdx.y][threadIdx.x] = B[(m * BLOCK_SIZE + threadIdx.y) * N + col];

        // 等待所有线程加载完成
        __syncthreads();

        // 计算这一块
        for (int k = 0; k < BLOCK_SIZE; k++)
            sum += As[threadIdx.y][k] * Bs[k][threadIdx.x];

        // 等待所有线程计算完成,再加载下一块
        __syncthreads();
    }

    if (row < M && col < N)
        C[row * N + col] = sum;
}
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
35

第5节:CUDA 错误处理——容易被忽略的坑 ​

异步执行和错误检查 ​
cpp
// ❌ 错误:cudaMalloc 的返回值不是错误码
float* d_a;
cudaError_t err = cudaMalloc(&d_a, N * sizeof(float));
if (err != cudaSuccess) {
    printf("CUDA malloc failed: %s\n", cudaGetErrorString(err));
}

// ✅ 正确:kernel 启动是异步的!
// cudaGetLastError() 可以检查 kernel 启动是否有错
add<<<blocks, threads>>>(d_a, d_b, d_c, N);
err = cudaGetLastError();  // kernel 启动后立即调用
if (err != cudaSuccess) {
    printf("Kernel launch failed: %s\n", cudaGetErrorString(err));
}

// ✅ 正确:等待 GPU 完成后再检查结果
cudaDeviceSynchronize();  // 等待 GPU 所有任务完成
err = cudaGetLastError();
if (err != cudaSuccess) {
    printf("Kernel execution failed: %s\n", cudaGetErrorString(err));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
完整错误检查封装 ​
cpp
// 封装成宏,方便使用
#define CHECK_CUDA(call)                                                  \
    do {                                                                  \
        cudaError_t err = call;                                          \
        if (err != cudaSuccess) {                                        \
            printf("CUDA error at %s:%d: %s\n",                         \
                   __FILE__, __LINE__, cudaGetErrorString(err));          \
            exit(EXIT_FAILURE);                                           \
        }                                                                 \
    } while (0)

// 使用
CHECK_CUDA(cudaMalloc(&d_a, N * sizeof(float)));
CHECK_CUDA(cudaMemcpy(d_a, h_a, N * sizeof(float), cudaMemcpyHostToDevice));
add<<<blocks, threads>>>(d_a, d_b, d_c, N);
CHECK_CUDA(cudaGetLastError());  // kernel 启动后立即检查
CHECK_CUDA(cudaDeviceSynchronize());  // 等待完成
CHECK_CUDA(cudaMemcpy(h_c, d_c, N * sizeof(float), cudaMemcpyDeviceToHost));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

第6节:完整的项目结构示例 ​

┌─────────────────────────────────────────────────────────────┐
│                    完整项目文件结构                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  matrix_mul_project/                                        │
│  ├── CMakeLists.txt                                        │
│  ├── include/                                               │
│  │   ├── matrix_mul.cuh         ← kernel 声明              │
│  │   └── common.h               ← 通用工具函数              │
│  ├── src/                                                   │
│  │   ├── main.cpp               ← Host 代码(C++)         │
│  │   └── matrix_mul.cu          ← Device 代码(CUDA)       │
│  └── build/                                                │
│      └── (编译输出)                                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cmake
### CMakeLists.txt
cmake_minimum_required(VERSION 3.18)
project(matrix_mul LANGUAGES CXX CUDA)

set(CMAKE_CUDA_STANDARD 17)
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
set(CMAKE_CUDA_ARCHITECTURES 80)  # A100

### CUDA Toolkit
find_package(CUDAToolkit REQUIRED)

### 头文件目录
include_directories(${CMAKE_SOURCE_DIR}/include)

### 可执行文件
add_executable(matrix_mul
    src/main.cpp
    src/matrix_mul.cu
)

target_link_libraries(matrix_mul PRIVATE CUDA::cudart)

### 启用 CUDA 扩展(CMake 自动处理 .cu 文件)
enable_language(CUDA)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bash
### 编译
cd matrix_mul_project
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release

### 运行
./matrix_mul
1
2
3
4
5
6
7
8

升华:CUDA 编程的核心心法 ​

┌─────────────────────────────────────────────────────────────┐
│                    CUDA 编程检查清单                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 线程组织                                               │
│     ✅ 每个线程处理一个或多个数据元素                        │
│     ✅ Block 维度设为 32 的倍数(warp 对齐)               │
│     ✅ Grid 大小 >= GPU SM 数量(充分利用硬件)             │
│                                                             │
│  2. 内存管理                                               │
│     ✅ cudaMalloc 分配 GPU 显存                             │
│     ✅ cudaMemcpy 传输数据(注意方向 HostToDevice)         │
│     ✅ cudaFree 释放显存                                    │
│                                                             │
│  3. 错误检查                                               │
│     ✅ 所有 CUDA 调用都要检查返回值                         │
│     ✅ kernel 启动后立即检查 cudaGetLastError              │
│     ✅ 验证结果前调用 cudaDeviceSynchronize                 │
│                                                             │
│  4. 性能意识                                               │
│     ✅ 线程数太少 → GPU 利用率低                           │
│     ✅ 线程数太多 → 寄存器溢出,效率下降                   │
│     ✅ 256 线程/block 是经验最优起点                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
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

"AI 可查 vs 必须理解"清单 ​

AI 可查:
✅ cudaMalloc/cudaMemcpy 的具体参数
✅ dim3 的具体用法
✅ CMake 中 CUDA 项目的具体配置

必须理解:
🔴 Host(CPU)负责调度,Device(GPU)负责计算
🔴 Grid / Block / Thread 的三层组织关系
🔴 线程索引计算:int i = blockIdx.x * blockDim.x + threadIdx.x
🔴 __global__ / __device__ / __host__ 的区别
🔴 kernel 启动是异步的,需要 cudaDeviceSynchronize 等待
🔴 为什么一个线程算一个输出元素 = 完美并行
1
2
3
4
5
6
7
8
9
10
11
12

学习状态:🟡 开始学习


完整迁入:原并行计算版 CUDA 模型 ​

CUDA 编程模型:Thread、Block、Grid 与内存协作 / CUDA Threads, Blocks, Grids, and Cooperative Memory Access ​

📅 创建时间:2026-07-20 🏷️ 标签:#CUDA #Kernel #ThreadBlock #GPU编程 📚 前置知识:[[05-gpu-architecture]] 📚 相关知识:[[/02-systems-and-performance/02-computer-architecture-and-hardware/04-cuda-programming-model]] [[/02-systems-and-performance/02-computer-architecture-and-hardware/05-cuda-kernel-and-memory]]


1. Host 与 Device ​

CUDA 程序通常同时运行在两个世界:

text
CPU / Host                         GPU / Device
准备数据                           执行大量并行线程
分配显存        ───────────────→   读取显存
启动 Kernel                        完成计算
读取结果        ←───────────────   写回显存
1
2
3
4
5

CPU 负责控制流程,GPU 负责执行 Kernel。一次 GPU 加速是否值得,必须把数据传输和 Kernel 启动成本也算进去。


2. 第一个 Kernel ​

cpp
__global__ void vectorAdd(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;
vectorAdd<<<blocks, threads>>>(a, b, c, n);
1
2
3
4
5
6
7
8
9

三个重要索引:

  • threadIdx:线程在线程块中的位置
  • blockIdx:线程块在网格中的位置
  • blockDim:每个线程块的尺寸

3. Grid、Block 与 Thread ​

text
Grid
├─ Block 0
│  ├─ Thread 0
│  ├─ Thread 1
│  └─ ...
├─ Block 1
└─ ...
1
2
3
4
5
6
7

设计原则:

  • Thread 处理最细粒度工作,例如一个元素
  • Block 是能够共享内存并进行块内同步的线程组
  • Grid 表示一次 Kernel 启动的全部线程块

不同 Block 之间默认不能通过普通 Barrier 同步,因此算法需要把跨 Block 协作放到后续 Kernel 或专门机制中。


4. 线程块怎样映射到硬件 ​

线程块被调度到 SM,内部线程按 Warp 分组。常见一维 Block 大小为 128、256 或 512,但没有对所有 Kernel 都最优的固定值。

选择 Block 大小时要考虑:

  • 是否为 Warp 大小的整数倍
  • 每线程寄存器数量
  • 每 Block Shared Memory 用量
  • Kernel 的访存和计算特征
  • 设备允许的最大线程数

Occupancy 高不等于性能一定高,它只表示活跃 Warp 相对于硬件上限的比例。


5. Shared Memory 协作 ​

线程块可以先把数据从 Global Memory 搬到 Shared Memory,再重复使用:

cpp
__global__ void tiledKernel(const float* input, float* output) {
    __shared__ float tile[256];
    int global = blockIdx.x * blockDim.x + threadIdx.x;

    tile[threadIdx.x] = input[global];
    __syncthreads();

    output[global] = computeWithNeighbors(tile, threadIdx.x);
}
1
2
3
4
5
6
7
8
9

__syncthreads() 要求 Block 中所有仍然参与执行的线程到达同步点。把它放进线程条件不一致的分支可能导致错误或死锁。


6. 异步执行与 Stream ​

Kernel 启动通常对 CPU 是异步的。CUDA Stream 表示设备上的有序任务队列:

text
Stream 0:拷贝 A → Kernel A → 回传 A
Stream 1:       拷贝 B → Kernel B → 回传 B
1
2

在硬件支持、数据独立且使用异步 API 的条件下,可以实现:

  • 数据传输与计算重叠
  • 多个 Kernel 并发
  • 双缓冲流水线

Pinned Host Memory 有利于异步 DMA 传输,但锁页内存是有限资源,不应无限申请。


7. 错误检查 ​

Kernel 错误可能在后续同步时才暴露。开发阶段至少检查:

cpp
kernel<<<blocks, threads>>>(...);
cudaError_t launchError = cudaGetLastError();
cudaError_t runtimeError = cudaDeviceSynchronize();
1
2
3

生产代码应统一封装 CUDA API 错误处理,并记录设备、尺寸和调用上下文。


8. 常见初学者问题 ​

  • 忘记检查边界导致越界访问
  • 频繁进行小块 Host-Device 拷贝
  • 每个小操作都启动一个 Kernel
  • 假设 Kernel 完成后 CPU 才继续执行
  • 在分支中错误使用块内 Barrier
  • 只关注线程数量,不关注访存模式
  • 用默认 Stream 隐式同步整个流水线

核心总结 ​

  • CUDA 用 Grid、Block、Thread 描述大规模并行工作。
  • Block 是共享内存与同步的基本协作范围。
  • Kernel 启动、显存分配和数据传输都是成本。
  • Stream 让传输与计算有机会重叠。
  • 正确性、错误检查和边界处理先于性能优化。

下一篇:[[07-parallel-patterns]] <!-- migrated-deep-dive:end -->

面试速答 ​

CUDA 与 GPU 的关系? GPU 是硬件;CUDA 是 NVIDIA 针对其 GPU 的平台、模型、工具链和库生态。

Thread/Block/Grid 与 Warp/SM 的关系? 前者是 CUDA 编程模型;Block 被调度驻留到 SM,其线程通常每 32 个组成 Warp 执行。

Runtime API 与 Driver API? Runtime 更高层易用,Driver 更底层、控制更细;二者最终都依赖驱动与设备。

自测 ​

  1. CUDA Toolkit、驱动和 Runtime 是否是同一个东西?
  2. 两个 Block 能否默认用 __syncthreads() 同步?
  3. Kernel 启动后为什么不能立刻假定 Host 上结果可用?

答案:不是;不能;启动常异步,需要由拷贝、事件或显式同步建立完成关系。

最后更新于:

Pager
上一篇8. C++ 多线程与 OpenMP
下一篇10. CUDA Kernel、内存与性能

持续记录,持续成长

Copyright © Tidenflow