编辑推荐: |
本文主要介绍了GPU的工作原理以及
计算指标与矩阵运算, 希望对你的学习有帮助。
本文来自于知乎 ,由火龙果软件Alice编辑推荐。 |
|
一、背景
随着AI大模型的发展,需要专业的高性能计算显卡来支持不断增长的计算规模,主要考虑以下几个方面:
- 需要支持神经网络模型的计算逻辑
- 权重共享
- 激活、全连接等算子的支持
- 支持高维张量的存储与计算
- 内存地址随机或自动索引
- 大批量数据的高效加载
- 支持常用模型结构
- conv,transformers
- 支持常见数据结构
- 提供不同的bit位数
- 支持低bit量化
- 在动态范围M-bits和指数E-bits
- 利用硬件提供稀疏计算
- 硬件上减少 0 值的重复计算
- 减少网络模型对内存的需求,稀疏化网络模型结构
- 支持高效的分布式计算
- 芯片间互连技术,提供 X00GB/s 带宽
- 支持CPU+GPU 双架构,为大规模 AI 和HPC异构平台提供高速带宽
- 专用大模型DSA IP模块,提供低比特快速计算
- 专用高速 Transformer 引擎
- 大模型以Transformer为基础结构进行堆叠,高速的Transformer计算
- 更低比特Transformer模块,并支持MoE构建万亿大模型
- 降低功耗
- 减少eachMAC功耗
- 避免无效 MACs 计算
- 减少耗能的数据格式搬运 >> 数据重用
二、计算指标与矩阵运算
1、指标
FLOPS
"FLOPS" 是 "Floating Point Operations Per Second" 的缩写,意为每秒浮点运算次数。它是衡量计算机系统或处理器性能的一种常见指标,特别是在科学计算、工程领域和高性能计算中。例如,一个处理器的性能为 1 TFLOPS,意味着它可以每秒执行 1 万亿次浮点运算。
MACs
Multiply–Accumulate Operations,乘加累积操作。1MACs包含一个乘法操作与一个加法操作(Ax+y)
优化:
- 去掉没有意义的MACs
- 增加对稀疏数据的硬件结构 sparse data
- 控制流控制和执行 control flow
- 节省时钟周期 save cycles
- 降低每次MACs的计算时间
- 增加时钟频率 clock frequency
- 增加PE单元计算能力,例如提高制程(7纳米->5纳米)
- 减少指令开销 instruction overhead
- 增加MACs并行计算能力
- 增加PE利用率
Accuracy
1.计算精度 (FP32/FP16 etc.)
2.模型结果精度 (ImageNet 78%)
优化:
- 能够处理各类型的无规则数据 >> 异构平台
- 能够应对复杂网络模型结构 >> 计算冗余性
Throughput
高维张量处理 (high dimension tensor)
实时性能 (30 fps or 20 tokens)
优化:
- 除了峰值算力,看计算单元的平均利用率 >> 负载均衡
- SOTA网络模型的运行时间 >> MLPerf
Latency
生成第一个 Token 的时间(Time To First Token (TTFT)
生成每一个输出 Token 的时间(Time Per Output Token (TPOT)
Latency = TTFT + TPOT *(要生成的 Token 数 - 1)
Latency 可以转换为 Tokens Per Second (TPS):TPS = (the number of tokens to be generated) / Latency。
优化:
- 通信时延对 MACs 的影响 >> 优化带宽
- Batch Size 大小与内存大小 >> 多级缓存设计
2、矩阵运算
常见矩阵运算:
- 分块-Tiling:
- 根据 Cache 大小来对矩阵进行分块 Tiling,最大程度重用数据和利用空间换时间
- GPU 上各种内存的访问速度为 Global memory << shared memory < register (local memory)Global memory 大而慢, shared memory 小而快,因此减少内存访问延迟的一个重要方向就是要尽量减少 global memory 的访问,其中一个常见的策略就是 Tiling —— 将数据分片,然后将每个小分片缓存到 shared memory 中。
- Tiling 技术通过将 global memory 中的元素加载到 shared memory 中以便多次使用,从而减少了对 global memory 的访问次数;一般情况下,如果分片大小为 K×K 个元素,则 global memory 的访问次数会减少 K 倍
GEMM :通用矩阵乘,是一种广泛用于深度学习神经网络模型的计算操作,通常在软件层面实现,并利用现代处理器的FMA指令来加速运算。
- FMA: 融合矩阵乘加,通过单个指令实现矩阵乘加。D=AB+C(tensor core)
硬件
现有库: Matrix Multiplication (GEMM)
◦ CPU: OpenBLAS, Intel MKL, etc.
◦ GPU: cuBLAS, cuDNN, etc.
实现逻辑:
◦ Lib 感知相乘矩阵的 Shape
◦ 选择最优的 Kernel 实现来执行
实现方法:
◦ Loop 循环优化 (Loop tiling)
◦ 多级缓存 (memory hierarchy)
- Macro-Kernel(宏内核): Macro-kernel 是指高级别的循环结构,负责管理多个 micro-kernel 的执行。在矩阵乘法中,它通常用于处理大规模的矩阵计算,例如管理整个矩阵乘法的批处理过程。
- Micro-Kernel(微内核): Micro-kernel 是指低级别的循环结构,负责执行实际的矩阵乘法操作。它通常用于管理单个矩阵块的计算,以实现更细粒度的并行化和优化。
三、GPU工作原理
GPU (Graphics Processing Units, GPUs),正如其全称“图形计算单元”,GPU的初衷主要是为了接替CPU进行图形渲染的工作。因为图像上的每一个像素点都需要处理,这项任务计算量相当大。尤其遇上一个复杂的三维场景,就需要在一秒内处理几千万个三角形顶点和光栅化几十亿的像素。不过,由于每个像素点处理的过程和方式相差无几,这项艰巨的任务可以靠并行计算来化解。
GPU的性能取决于其内部的核心数量、时钟频率以及RAM容量。GPU包含多个核心,每个核心都可以独立执行命令。每个核心具有自己的存储器,用于存储指令和数据。
GPU的工作原理流程如下:
1. GPU会接收来自CPU的指令,并把它们分发到多个核心中进行处理。
2. GPU会把处理后的数据传输回 CPU,以便CPU可以使用它。
GPU vs CPU
• GPU几乎主要由计算单元ALU组成,仅有少量的控制单元和存储单元。GPU采用了数量众多的计算单元和超长的流水线,但只有非常简单的控制逻辑并省去了Cache。
• CPU不仅被Cache占据了大量空间,而且还有有复杂的控制逻辑和诸多优化电路,相比之下计算能力只是CPU很小的一部。
由于设计原则不同,二者擅长的场景有所不同:
- CPU 在连续计算部分,延迟优先,CPU 比 GPU 单条复杂指令延迟快10倍以上。
- GPU 在并行计算部分,吞吐优先,GPU 比 CPU 单位时间内执行指令数量10倍以上。
GPU设计目标是最大化吞吐量 (Throughout),比单任务执行快慢,更关心并行度 (parallelism),即同时可以执行多少任务;CPU则更关心延迟 (latency) 和并发 (concurrency)。
进一步可以具体化适合 GPU 的场景:
- 计算密集:数值计算的比例要远大于内存操作,因此内存访问的延时可以被计算掩盖。
- 数据并行:大任务可以拆解为执行相同指令的小任务,因此对复杂流程控制的需求较低。
GPU 是一个大型的存储器,有一部分的线程在等待着数据,有一部分线程在等待被激活计算,有一部分正在计算的过程当中。
线程原理
kernel
主设概念: CUDA引入主机端(host)和设备(device)概念。CUDA 程序中既包含host程序,又包含device程序。
互相通信: host与device之间可以进行通信,这样它们之间可以进行数据拷贝。
CUDA 执行流程中最重要的一个过程是调用CUDA的核函数来执行并行计算,kernel是CUDA中一个重要的概念。
• 在 CUDA 程序构架中,Host 代码部分在CPU上执行,是普通C代码;当遇到数据并行处理的部分,CUDA 就会将程序编译成GPU能执行的程序,并传送到GPU,这个程序在CUDA里称做核(kernel)。
CUDA Kernel函数:是数据并行处理函数(核函数),在GPU上执行时,一个Kernel对应一个Grid,基于GPU逻辑架构分发成众多thread去并行执行。kernel 用 __global__符号声明,在调用时需要用 <<<grid, block>>> 来指定kernel要执行及结构。
CUDA Stream流:Cuda stream是指一堆异步的cuda操作,他们按照host代码调用的顺序执行在device上。
thread
在GPU编程中,线程是最基本的执行单元。一个GPU程序会同时启动成千上万个线程来执行任务
warp
GPU 的 每一行由1个控制单元加上若干计算单元所组成,这些所有的计算单元执行的控制指令是一个 。这其实就是个非常典型的 "单指令多线程机制(SIMT)" 。
单指令多线程机制是说:多个线程同时执行相同的指令序列,但是每个线程可以处理不同的数据。这些线程通常被分组成更小的线程块,每个线程块中的线程可以协调执行相同的指令。
warp是硬件级别上的调度单位,一个 warp 包含32个并行 thread,这些 thread 以不同数据资源执行相同的指令。
Block
- 块是线程的集合,它们被组织成一个工作单元,可以共享内存和同步。
- 在GPU编程中,通常会将线程划分成若干个块,以便更有效地管理和协调线程的执行。
- 块内的线程可以进行协作和通信,通常通过共享内存来提高性能。
- Block 间并行执行,并且无法通信,也没有执行顺序
Grid
多个block则会再构成grid。kernel 在 device 上执行时,实际上是启动很多线程,一个kernel 所启动的所有线程称为一个网格(grid)。同一个网格上的线程共享相同的全局内存空间。
当一个 kernel 被执行时,grid 中的线程块被分配到 SM (多核处理器) 上,一个线程块的 thread 只能在一个SM 上调度,SM 一般可以调度多个线程块,大量的 thread 可能被分到不同的 SM 上。每个 thread 拥有它自己的程序计数器和状态寄存器,并且用该线程自己的数据执行指令,这就是所谓的 Single Instruction Multiple Thread (SIMT),如下图所示。
每个 thread 都有自己的一份 register 和 local memory 的空间。同一个 block 中的每个 thread 则有共享的一份 share memory。此外,所有的 thread (包括不同 block 的 thread) 都共享一份 global memory。不同的 grid 则有各自的 global memory。
从软件的角度来讲:
- 线程处理器 (SP) 对应线程 (thread)。
- 多核处理器 (SM) 对应线程块 (thread block)。
- 设备端 (device) 对应线程块组合体 (grid)。
对于 A100 来说,GPU 内部有 22 万多个线程,其实线程是超配的,那为何这么做呢?一切都是为了提高 GPU 的利用率,同时也可以更好地应对计算复杂度的变化
缓存机制
硬件
GPU 内存硬件的分类,按照是否在芯片上面可以分为片上 (on chip) 内存和片下 (off chip) 内存。
片上内存 主要用于 缓存 (cache) 以及少量特殊存储单元(如texture)。特点是速度快,存储空间小;
片下内存 主要用于全局存储 (global memory) 即常说的显存 ,特点是速度相对慢,存储空间大。不同于 CPU 系统内存可扩展的设计,GPU 的整体内存大小是固定的,在选择好显卡型号后就会确定好,包括缓存和全局存储。
在磁盘/硬盘(Disk/SSD)上面的数据传入到 GPU 的内存要经过: 硬盘 -> 系统内存 -> GPU 内存 的过程。这个速度非常慢,要极力避免这种传输。
系统存储:
- L1/L2/L3:多级缓存,位置在 CPU 芯片内部;
- System DRAM:动态 RAM,CPU 芯片外部内存,如内存条
- Disk/Buffer:外部存储,如磁盘或者固态硬盘。
GPU 设备存储:
- L1/L2 cache:多级缓存,位置在 GPU 芯片内部;
- GPU DRAM:通常所指的显存;
传输通道:
- PCIE BUS:PCIE标准的数据通道,数据就是通过该通道从显卡到达主机;
- BUS: 总线。计算机内部各个存储之间交互数据的通道;
- PCIE-to-PCIE:显卡之间通过PCIE直接传输数据;
- NVLINK:NVIDIA 公司推出的、用于显卡之间的专用的超高速数据传输通道。
PCIE 6.0 的最大理论传输带宽为 128GB/s ;而第四代 NVLINK 的能够提供 GPU 之间 900GB/s 的带宽。二者有接近一个数量级的差距。
架构
在 NVIDIA A100 中,HBM Memory(80GB) 就是我们通常说的显存,在这里我们把一些寄存器文件(Register File)也当作缓存。实际执行单元(SM)希望能够快速获取数据,于是实际执行单元会从寄存器中读取 L2 Cache 的内容。另外一方面呢,希望 L2 Cache 与 显存更近,当 L2 Cache 未命中时,GPU 会从显存中寻找数据。如果在显存还没有找到,就需要通过 PCIe 总线在内存中寻找,但由于 PCIe 的内存带宽非常低(比显存带宽低20倍),因此这会导致时延的大大增加。
多级缓存的形式降低了由于内存传输导致的时延,提高了GPU的算力利用率和总体计算时间。同时,缓存的带宽和容量也在不断增大,以满足GPU对高速数据访问的需求。
按照存储功能进行细分,GPU 内存可以分为:局部内存(local memory)、全局内存(global memory)、常量内存(constant memory)、共享内存(shared memory)、寄存器(register)、L1/L2 缓存等。
其中全局内存、局部内存、常量内存都是片下内存,储存在 HBM 上。所以我们说 HBM 的 大部分 作为全局内存。
关于 SRAM 与 DRAM :
RAM 分为静态 RAM(SRAM)和动态 RAM(DRAM)。SRAM 只要存入数据后,即使不刷新也不会丢失记忆;而 DRAM 的电容需要周期性地充电,否则无法确保记忆长存。
DRAM 密度高、成本低、访问速度较慢、耗电量大。SRAM 则刚好相反。因此 SRAM 首选用于带宽要求高,或者功耗要求低的情境。如:CPU Cache、GPU On-Chip Buffer。DRAM 则一般用于系统内存、显存。
片上 (on chip) 内存-SRAM
L1/L2缓存
L2 缓存可以被所有 SM 访问,速度比全局内存快;L1 缓存用于存储 SM 内的数据,被 SM 内的 CUDA cores 共享,但是跨 SM 之间的 L1 不能相互访问。
合理运用 L2 缓存能够提速运算。A100 的 L2 缓存能够设置至多 40MB 的持续化数据 (persistent data),能够拉升算子 kernel 的带宽和性能。Flash attention 的思路就是尽可能地利用 L2 缓存,减少 HBM 的数据读写时间。
寄存器
寄存器(register)是线程能独立访问的资源,它是片上(on chip)存储,用来存储一些线程的暂存数据。寄存器的速度是访问中 最快 的,但是它的容量较小,只有几百甚至几十 KB,而且要被许多线程均分。
共享内存
共享内存(shared memory) 是一种在线程块内能访问的内存,是片上(on chip)存储,访问速度较快。
共享内存主要是缓存一些需要反复读写的数据。
注:共享内存与 L1 缓存的位置、速度极其类似,区别在于共享内存的控制与生命周期管理与 L1 不同:共享内存受用户控制,L1 受系统控制。共享内存更利于线程块之间数据交互。
片下 (off chip) 内存
全局内存
全局内存(global memory)能被 GPU 的所有线程访问,全局共享。它是片下(off chip)内存,前面提到的硬件 HBM 中的大部分都是用作全局内存。跟 CPU 架构一样,运算单元不能直接使用全局内存的数据,需要经过缓存,其过程如下图所示:
局部内存
局部内存 (local memory) 是线程独享的内存资源,线程之间不可以相互访问。局部内存属于片下内存,所以访问速度跟全局内存一样。它主要是用来应对 寄存器不足 时的场景,即在线程申请的变量超过可用的寄存器大小时,nvcc 会自动将一部数据放置到片下内存里。
常量内存
常量内存(constant memory)是片下(off chip)存储,但是通过特殊的常量内存缓存(constant cache)进行缓存读取,它是只读内存。
常量内存主要是解决一个 warp scheduler 内多个线程 访问相同数据 时速度太慢的问题。假设所有线程都需要访问一个 constant_A 的常量,在存储介质上 constant_A 的数据只保存了一份,而内存的物理读取方式决定了多个线程不能在同一时刻读取到该变量,所以会出现先后访问的问题,这样使得并行计算的线程出现了运算时差。常量内存正是解决这样的问题而设置的,它有对应的 cache 位置产生多个副本,让线程访问时不存在冲突,从而保证并行度。
SM(SP)
从 G80 提出的概念,中文称流式多处理器,核心组件包括CUDA核心、共享内存、寄存器等。SM包含许多为线程执行数学运算的Core,是 NVIDA 的核心。
• 在CUDA中, 可以并发地执行数百个线程。一个 block 上线程是放在同一个 SM,一个 SM 的有限 Cache 制约了每个 block 的线程数量。
主要包括:
- CUDA Core:向量运行单元 (FP32-FPU、FP64-DPU、INT32-ALU); 最开始叫SP(Streaming Processor) , 是GPU最基本的处理单元,在fermi架构开始被叫做 CUDA core 。Volta 架构时期取消了CUDA Core,变成了单独的FPU 和ALU。
- Tensor Core:张量运算单元(FP16、BF16、INT8、INT4);
- Special Function Units:特殊函数单元 SFU(超越函数和数学函数,e.g. 反平方根、正余弦等);
- Warp Scheduler:线程束调度器(XX Thread / clock);
- Dispatch Unit:指令分发单元(XX Thread / clock);
- Multi level Cache:多级缓存(L0/L1 Instruction Cache、L1 Data Cache & Shared Memory);
- Register File:寄存器堆;
- Load/Store:访问存储单元LD/ST(负责数据处理);
- GPC —— 图形处理簇,Graphics Processing Clusters
- TPC —— 纹理处理簇,Texture Processing Clusters
- SM —— 流多处理器,Stream Multiprocessors
- HBM —— 高带宽存储器,High Bandwidth Memory
- 包含关系为:GPC > TPC > SM > CORE
CUDA Core
GPU并行模式实现深度学习功能过于通用,最常见Conv/GEMM 操作,依旧要被编码成 FMA,硬件层面还是需要把数据按:寄存器-ALU-寄存器-ALU-寄存器的方式来回搬运。具体来说是把乘和加分开执行,把数据放到寄存器,执行乘操作,得到的结果再放到寄存器,执行加操作,再将得到的结果放到寄存器;
Tensor Core
不同于nvidia以往的cuda core(全浮点型),Tensor core是近几年推出来的、混合精度的、将累加和累乘放在一起的计算硬件;
混合精度 是指在底层硬件算子层面,使用半精度(FP16)作为输入和输出,使用全精度(FP32)进行中间结果计算从而不损失过多精度的技术。
这个底层硬件层面其实指的就是Tensor Core,所以 GPU 上有 Tensor Core 是使用混合精度训练加速的必要条件。
Tensor Core 跟卷积计算或者 GEMM 计算之间的映射
• 卷积的计算可以转换为两个矩阵相乘的求解,得到最终的卷积计算结果。
• GEMM计算可以被成批地放在一起,作为单个大型矩阵乘法运算运行。
Tensor core 是在 Volta 以及之后的架构中才有的。相比于CUDA core,它可以提供更高效的运算。
- 2017年5月发布的NVIDIA Volta 架构中的第一代 Tensor Core ,Tensor Core是一种新型处理核心,它执行一种专门的矩阵数学运算,适用于深度学习和某些类型的HPC。每个Tensor Core可执行64次融合乘法加法,一个SM的所有8个Tensor core每时钟共执行512次FMA或1024次单个浮点运算,Tesla V100 Tensor Core可为训练和推理提供125 Tensor TFLOPS。
- 每个 Tensor Core 每周期能执行 4x4x4 GEMM ,64 个 FMA。执行运算 D=A*B+C ,其中A、B、C 和 D是 4×4 矩阵。 矩阵乘法 输入 A 和 B 是 FP16 矩阵,而 累加矩阵 C 和 D 可以是 FP16或 FP32 矩阵。
- 每个 Tensor Core 每个时钟执行 64 个 FP32 FMA 混合精度运算,SM中8个 Tensor Core,每个时钟周期内总共执行512 个浮点运算。因此在 AI 应用中, Volta V100 GPU的吞吐量与Pascal P100 GPU相比,每个 SM 的 AI 吞吐量增加了8 倍,总共增加了12倍
- 对于Volta架构,SM被划分为四个处理块或子核。对于每个子核,调度器每个时钟向本地分支单元(BRU)、Tensor Core阵列、数学分派单元或共享MIO单元发出一个warp指令,这就首先阻止了Tensor运算和其他数学运算同时进行。在利用两个Tensor Core时,warp调度器直接发出矩阵乘法运算,并且在从寄存器接收输入矩阵之后,执行4*4*4矩阵乘法。待完成矩阵乘法后,Tensor Core再将得到的矩阵写回寄存器。给定A*B+C Tensor Core操作,片段由A的8个FP16*2元素(即16个FP16元素)和B的另外8个FP16*2元素,以及FP16累加器的4个FP16*2元素或 FP32累加器的8个FP32元素组成。从概念上讲,Tensor Core在4*4子矩阵上运行,以计算更大的16*16矩阵。warp线程被分成8组,每组4个线程,每个线程组连续计算一个8*4块,总共要经过4组的过程,每一个线程组都处理了目标矩阵的1/8。
- 第二代 Turing 架构tesor core支持的精度FP16、INT8、INT4、INT1。每个张量核心可以执行 64 个浮点融合乘法加法( FMA )操作,每个时钟使用 FP16 输入。一个 SM 中的八个张量核心每时钟执行 512 个 FP16 乘法和累加运算,或每个时钟总共执行 1024 次浮点运算。新的 INT8 精度模式以两倍的速率工作,即每时钟 2048 次整数运算。可支持112 TFLOPS FP16, 228 TOPS INT8, 455 TOPS INT4。
- 第三代 Ampere 架构tensor core支持的精度:FP64、TF32、bfloat16、FP16、INT8、INT4、INT1。每个 TensorCore 在每个时钟周期支持的混合精度矩阵乘加从 Volta 的 4 x 4 x 4 进化到 8 x 4 x 8
- 第四代Hopper架构tensor core支持的精度:FP64、TF32、bfloat16、FP16、FP8、INT8。与上一代16位浮点计算相比,Tensor Core在同等数据类型上计算速度是A100 SM的MMA的2倍,而在使用FP8数据类型时,计算速度是A100的4倍。每个 TensorCore 在每个时钟周期支持的混合精度矩阵乘加进化到 4 x 8 x 16。
三、发展历程
1、4090(Ad102)
Ad102架构:拥有 12 个 GPC(图形处理簇,每个 GPC 接近于一枚完整图形流水线的小 GPU,GA102有8组),每个 GPC 里包含有 6 个 TPC(纹理处理簇),一个Raster Enginer,每个 TPC 里包含有两个 SM(流式多处理器)合计 144 个 SM,每个 SM 内包含有 4 组各 32 个 CUDA Core(按照 OpenCL 术语则是 PE)的 SubCore,因此,一个完整的 AD102 GPU 一共有 18432 个 CUDA Core。相对于完整版的 AD102,4090禁用了其中一个 GPC,因此 GeForce RTX 4090 的 CUDA Core 数量是 16834 个 CUDA Core。
2、A100(GA100)
整个 GPU 有8个 GPC(图形处理集群),单个GPC包含8组TPC,每个TPC包含2个 SM(流式多处理器),GPC 可以被认为是一个独立的 GPU。SM是GPU的核心计算单元,GPU硬件的并行性就是由SM决定的。把大量这样的 SM 排布在一起,将它们连接在 L2 Cache 即显存和全局的调度器(GigaThread Engine)上,再为整张芯片设置与外部通信的线路——这就是用于 Data Center 的安培架构显示核心 GA100 的所有组成成分
每个SM包含4个处理单元,它们共用这个 SM 的 L1 Instruction Cache(一级指令缓存)、L1 Data Cache(一级数据缓存/共享内存)、和4个Tex(内含Texture cache)。每个处理单元包含1个 Warp Scheduler(每个 Warp最多同时执行 32 个 thread),1个Dispatch Unit,8个FP64 Core, 16个FP32 Core, 16个INT32 Core, 1个Tensor Core, 8个Load/Store units (LD/ST Unit),4个Special Function Units (SFU)用于运算超越函数(sin、cos、exp、log……),寄存器文件,L0指令缓存。Tensor Core 有着专门设计的硬件结构,可以把整个矩阵都载入寄存器中批量运算,有十几倍的效率提升,Tensor Core支持(FP16,BF16,TF32,FP64,INT4,INT8)的运算类型。
|