2.1 并行计算基础认知
并行计算
随着大模型、三维游戏、科学计算和图像处理等应用不断发展,计算机需要处理的数据量越来越大,单次任务中包含的运算数量也越来越多。
以大模型和游戏画面渲染为例,模型在训练和推理过程中需要执行大量矩阵乘法。一个矩阵乘法通常包含成千上万次,甚至更多相似的乘加运算。游戏画面渲染时,屏幕上的大量像素、顶点和纹理也需要同时进行坐标变换、光照计算和颜色处理。
这些应用虽然计算量很大,但其中许多运算具有两个共同特点:第一,整体任务可以被拆分为大量规模较小的子任务;第二,这些子任务之间相对独立,可以在同一时间执行。
这就是我们所说的并行计算,也就是指将一份庞大的整体任务,拆解为大量可同时执行的小型子任务,利用多计算核心同步运算,缩短整体耗时。
并行计算通常需要满足下面的前提条件:
- 任务体量足够庞大,拆分后并行收益远大于调度、数据传输开销。(任务量小,没必要拆分)
- 子任务具有一定独立性,不存在强制的严格执行顺序,也没有强数据依赖。(需要同时处理)
- 具有重复性或规律性,多个子任务执行相同或相似的操作。(最好一套代码共用,而不是每个任务单独写一个)
对于重复性或者规律性,我们很容易想到,能否通过一段代码来完成所有的并行操作?当然可以!我们可以让多个执行线程使用同一套指令,同时处理不同的数据,来达到我们的并行处理的目的。
这种执行方式就是 SIMT:单指令多线程。它表示大量线程可以执行同一段程序,但每个线程拥有自己的编号、寄存器状态和待处理数据。
CPU与GPU
用 CPU 来完成 SIMT 显然是不可行的:CPU 是通用处理器,擅长处理复杂逻辑、操作系统调度、分支判断和串行控制流程。现代 CPU 虽然也支持多核和多线程,能够执行并行计算。但 CPU 的核心数量通常相对较少,并且每个核心内部包含复杂的控制逻辑、大容量缓存、分支预测和乱序执行等硬件。它的设计目标是让少量线程能够快速处理复杂任务。
大模型、矩阵运算和图形渲染主要由大量简单、重复且相互独立的运算组成,如果只使用少量 CPU 核心,即使性能较强,也需要分批执行大量子任务。GPU 则把更多硬件资源用于计算单元,而不是复杂的控制逻辑。与 CPU 相比,GPU 通常包含大量规模较小的计算核心,可以同时运行大量线程。
把 CPU 比作少数大学生,GPU 比作成千上万名小学生:大学生能力强,能独立完成难题,对应 CPU 擅长复杂的任务。但大学生人手特别少,如果布置上万道一样的简单口算题,就算每人不停做题,也得等到猴年马月去。而成千上万的小学生,哪怕只会做简单计算题,但是因为数量多,可以同时动笔,很快就能全部做完。
CPU 和 GPU 的具体对比可以见下表:
| 对比项 | CPU | GPU |
|---|---|---|
| 核心数量 | 核心数量较少 | 计算核心数量多 |
| 核心设计 | 单个核心功能复杂,通用性强 | 单个核心相对简单,强调并行计算能力 |
| 缓存设计 | 缓存容量较大 | 单个计算核心可使用的缓存资源相对较少 |
| 设计目标 | 追求单个任务的快速响应 | 追求大量任务的整体吞吐量 |
| 适合任务 | 复杂逻辑、程序控制、分支较多的任务 | 大量重复、规则且可以并行处理的任务 |
什么是CUDA
GPU 具有强大的并行硬件,但程序员仍然需要描述以下内容:哪些代码在 GPU 上执行;需要启动多少个线程;每个线程处理哪些数据;CPU 和 GPU 之间如何传递数据;不同线程之间如何协作和同步。如果只能通过底层硬件指令直接控制 GPU,编程难度会非常高。为了解决这一问题,NVIDIA 推出了 CUDA。
CUDA 是 NVIDIA 提供的并行计算平台和编程模型。它允许开发者使用类似 C/C++ 的方式编写在 NVIDIA GPU 上执行的程序。在 CUDA 程序中,通常由 CPU 负责程序控制、内存管理和任务提交,由 GPU 负责执行适合大规模并行的计算任务。
接下来,我们先学习 CUDA 中的一些基础概念,编程模型,环境配置,然后完成我们第一个 CUDA 程序的编写和运行。
本文内容主要来自个人学习与实践总结,受限于个人技术水平,难免存在理解不准确或表述疏漏等错误。 若您发现问题,或愿意就相关内容进一步交流,欢迎通过邮箱 571467648@qq.com 与我联系。感谢您的阅读与指正。