一、异构计算概念

  • 定义:不同计算架构协同工作即为异构(CPU+GPU 最常见,CPU+FPGA、CPU+DSP 也常见)。
  • 特点
    • GPU → 并行度高,适合大规模数据计算
    • CPU → 控制逻辑强,适合复杂流程
  • 应用复杂度高:需要开发者手动管理 计算分工、控制与数据传输

二、CPU 与 GPU 架构差异

  • CPU:少量复杂核心,强调低延迟,适合复杂逻辑与串行任务。
  • GPU:成百上千个轻量核心(SM 结构),强调高吞吐,适合数据并行。
  • 通信方式:通过 PCIe 总线连接,数据传输可能成为性能瓶颈

三、CUDA 平台概述

  • 定位:基于 NVIDIA GPU 的完整计算平台(硬件 + 软件 + 工具链)。
  • 语言支持:CUDA C (ANSI C 扩展,新增关键字、API)
  • 平台系列
    • Tegra → 嵌入式
    • GeForce → 消费级图形、游戏
    • Quadro → 专业图形工作站
    • Tesla → 高性能计算(HPC)
  • 性能指标
    • CUDA 核心数
    • GPU 显存大小
    • 峰值计算能力 (GFLOPS)
    • 内存带宽

四、CUDA 架构 & 计算能力

  • 计算能力 (Compute Capability) → 标记 GPU 架构代际
    • 1.x Tesla
    • 2.x Fermi
    • 3.x Kepler
    • 4.x Maxwell
    • 5.x Pascal
    • 6.x Volta

五、CUDA 编程模型

  • 代码分两部分
    • Host 端(CPU):控制、数据传输
    • Device 端(GPU):核函数计算
  • 关键语法
    • __global__ → 声明核函数
    • <<<...>>> → 启动核函数时配置执行参数
    • cudaDeviceReset() → 同步 GPU 与 CPU,保证 GPU 完成计算
  • 典型程序流程
  1. 分配 GPU 内存
  2. 主机 → 设备数据传输
  3. 执行核函数(GPU 计算)
  4. 设备 → 主机传回结果
  5. 释放内存

六、第一个CUDA程序:Hello World!

#include <stdio.h>

普通的 C 头文件,说明CUDA C 是基于 C 的扩展,所以大部分 C 语法、库都能用。

__global__ void hello_world(void) {
  printf("GPU: Hello world!\n");
}

核函数(Kernel Function)

  • __global__:CUDA 关键字,告诉编译器这是一个“核函数”,即在 GPU 设备端执行
  • 函数定义规则
    • 只能用 void 返回值(通常不返回值,结果写进内存)。
    • 可以带参数(后面学习数据传输时会用到)。
  • 内容:这就是 GPU 干的活。在这里,简单地 printf,以后就换成矩阵加法、图像处理等。
  • 重点理解
    • 写法很像普通函数,但运行在 GPU 上
    • 一个核函数会被 成千上万个线程同时执行(这里是 10 个线程)。
int main() {
  printf("CPU: Hello world!\n");

主机端代码(Host Code)

  • 这是普通的 C 主程序,运行在 CPU 上
  • 作用:控制 GPU,做数据准备、调用核函数、取回结果。
  • 在 CUDA 程序里,CPU 是“指挥官(host)”,GPU 是“士兵(device)”
hello_world<<<1,10>>>();

启动核函数

  • CUDA 扩展语法 <<<...>>>,称为 执行配置(Execution Configuration)
    • 格式:
kernel_name<<<numBlocks, threadsPerBlock>>>(args...); 
    • numBlocks:多少个线程块 (Block)
    • threadsPerBlock:每个线程块多少个线程 (Thread)
  • 在这个例子里:<<<1,10>>> = 1 个 Block,每个 Block 有 10 个线程 → 总共 10 个线程并行执行
  • 结果:这行代码会让 GPU 同时启动 10 个线程,每个线程都执行一次 hello_world()
cudaDeviceReset(); // 保证 GPU 输出完成

同步与收尾

  • 为什么要写?
    CUDA 默认 CPU 和 GPU 异步执行 → CPU 把任务交给 GPU 后立刻往下跑,不会等 GPU 完成。
    如果不加 cudaDeviceReset(),CPU 可能在 GPU 打印前就退出,导致 GPU 的结果丢失。
  • 这行函数会强制等待 GPU 完成,并清理 GPU 状态。
 return 0;
}

CUDA 程序通用蓝图(新手必须记住的 5 大步骤)

无论是 Hello World 还是复杂的矩阵运算,CUDA 程序几乎都遵循这个范式

  1. 主机端准备 (CPU)
  • #include 需要的库
  • 定义数据,分配 CPU 内存
  • 分配 GPU 内存 (cudaMalloc)
  • 把数据从 CPU 传给 GPU (cudaMemcpy)
  • 编写核函数 (GPU)
  • __global__ 修饰
  • 核函数只描述 单个线程要干的事
  • GPU 会有成千上万个线程一起跑这段代码
  • 启动核函数
  • <<<gridDim, blockDim>>> 配置线程数量
  • Grid(网格) = 线程块的集合
  • Block(线程块) = 线程的集合
  • 每个线程用内置变量 threadIdxblockIdx 来确定自己负责的数据
  • 收尾与结果传回
  • GPU 执行完毕 → 把结果传回 CPU (cudaMemcpy)
  • 释放 GPU 内存 (cudaFree)
  • 同步和错误处理
  • 需要同步 (cudaDeviceSynchronizecudaDeviceReset)
  • 任何 CUDA API 调用后建议加错误检查

对比总结

  • CPU 程序:写主函数,顺序执行。
  • CUDA 程序:两部分 →
    • Host 代码(控制 + 数据传输)
    • Device 代码(核函数,海量线程并行)
  • 思考模式变化:不再写“一个任务”,而是写“单个线程该干什么”,剩下交给 GPU 并行调度。
CPU(main) ----控制/数据----> GPU(核函数)
     |                            |
     |--- printf("CPU...")        |--- 每个线程 printf("GPU...")
     |--- 配置 <<<Grid,Block>>>   |
     |--- 同步等待                 |

七、CPU vs GPU 线程模型

  • CPU 线程:重量级,切换开销大
  • GPU 线程:轻量级,成千上万,切换几乎无开销
  • 设计差异
    • CPU → 降低延迟
    • GPU → 提高吞吐量

八、性能关键点

  • 数据局部性
    • 空间局部性(访问相邻数据)
    • 时间局部性(数据复用频率)

  • CUDA 性能模型
    • 线程层次结构(Grid、Block、Thread)
    • 内存层次结构(寄存器、共享内存、全局内存)

九、CUDA 开发工具

  • Nsight IDE(集成开发环境)
  • CUDA-GDB(调试器)
  • Visual Profiler / Nsight Systems(性能分析)
  • CUDA-MEMCHECK(内存调试)
  • GPU 管理工具

Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐