前言
这几天,GTC2020上,Nvidia发布了最新的Ampere架构。Ampere架构的改进重心,大部分放在AI上的改动,包括第三代的Tensor Core,以及相关的调度性能,以及互联等。它展示了在加强算力,特别是TC (TC == Tensor Core,下同)算力的情况下,整个系统的改进。
整体上,可以看到NV的GPU向向AI方向快速发展,看起来跟随市场的必然选择。转去吃AI芯片的蛋糕。原CUDA Core并行度增加放缓,A100所有大的改动,看起来都是以AI计算为主要目的,所以Tensor Core以及SM里AI加速的模块和功能大大加强。在数据中心AI芯片这块,弯道超车时间快结束了。基本上大家都回到了vector+tensor混合架构上的直线竞争。
这样,GPGPU逐渐成为了通用和专用混合的架构形态
•Core 架构:CUDA vector core + Tensor core
•Memory system:传统的cache system + AI memory system
•Scale and connection:scale up + scale out in one GPU
•Program Model:original CUDA + WMMA
异构的难题在于利用率上:NV的一些提高效率的改变,包括
•资源动态分配,L1C, L2C, MIG
•软硬件结合
这样,软件栈任务更重了。
因为我一直在GPU/AI软件架构方面工作,我会比较多的从软硬件结合,编程模型,软件上层应用的角度出发来考虑问题。希望给大家不同的视野。
下面从几个方面来学习和思考:
- 核心算力
- A100如何大幅度提升算力?
- 数据带宽
- 如何解决算力高涨下的带宽问题?
- 计算效率
- 如何提高计算效率?
核心算力
A100的算力提升主要集中在AI应用需要的FP16精度以下的类型。
- 传统FP32和FP64只有1.25x的增长。
- FP16有了2.5x的增长 (both CUDA core and Tensor core)
- 通过TF32的引入,让FP32(应用层)的数据处理大幅提升
- 再通过Sparsity的优化,对TC的几个数据类型又double了算力
Tensor core精度
- TF32 TC
- FP16/FP32的混合精度训练问题比较多,使用很麻烦,给软件栈和框架都带来不小的难题。(大家可以看一下混合精度训练的实现和问题一些材料)
- FP32 in/out的计算复杂,算力小
- TF32 TC能很好的解决FP16/FP32的混合精度的问题,又能以较小的代价,获得比较好的精度。
Fine-grained structured sparsity
非常棒的一个“快速”解决方案。之前我们关于AI芯片加速sparsity的优化想法,都是局限在"被动"sparsity的方面,也就是上层应用和框架主动做剪枝以后,针对稀疏矩阵做被动的优化。难度很大。原因是各种剪枝出来的数据没有共同特性,因此硬件上很难实现通用的加速。
A100的方法,是主动(由软件)提供固定结构化的剪枝,对训练好后的Dense权重剪枝后,再稍做Fine-tuning,这样得到的稀疏矩阵可以直接使用针对这一特定结构的压缩,硬件加速推理。获得近乎两倍的性能提升。精度方面,50%的剪枝,应该能保证足够的精度。他们应该是论证过的。
整个方法是比较典型的软硬件结合的思路:
- 软件提供包装好的4选2的剪枝方案,(软件实现,可升级算法,结合硬件)
- 简单的Fine-tuning训练
- 压缩剪枝后的weight,得到非零数据和Index,用于推理
- 提升
- 减少一半MMA计算
- weight数据,从host->device就开始节省带宽
- activation
- 根据不同的实现,可以节省L1开始,或者L2开始的带宽
数据带宽
A100算力的暴涨,应该是让原来的存储系统,特别是SM内的存储设计跟不上了。
特别的是TF32 TC的引入,带宽需求和FP32一样,但是算力上大了好多。以Tensor Core FP16作为对比,同样使用Tensor Core,同样的操作(Ops/byte ratio),要保证相同的计算吞吐,需要的存储带宽大大地增长了。
- V100,FP16,FLOPS=125T,input data type FP16
- A100,TF32 TC,FLOPS=312T,input data type FP32,
因此,同样利用率的情况下,大概就需要5倍(312/125*2)的带宽了,在打开Sparsity的功能后,还需要更多的带宽,因为Sparse Matrix只压缩weight matrix。
作为对比基础,先给出Volta的存储体系:
数字上的增长
- HBM2
- 16GB to 40GB, achive 1.6TB/sec, 73% increase.
- L2 cache
- 6M L2 to 40MB, about 7x size, 2.3x read bandwidth with a new partitioned crossbar structure(没有细节,还不理解,后续继续学习)
- L1 data cache and Shared memory
- 128 KB to 196 KB,1.5x larger
从数字上看,整个系统的吞吐/容量的纯增长,基本是在2倍左右,偏下。这些改进,相对CUDA core算力1.25x-2.5x的改进,其实是差不多足够了。但是,对于Tensor Core 5倍以上的需求增长,远远不够,所以需要其他架构的改进和新的特性来进一步支持算力。另外,为什么L2 cache急剧地增加到了40MB?
架构和特性的改进
思考
在理解这些改动之前,一定要充分地思考一下,AI特别是DNN的数据流特点,以及计算的特点。然后可以发现,主要的这些改进,都是充分考虑Tensor core的数据和计算特点,给Tensor Core做一些快速,简洁,或者并行性更高的通道。而且,有些改进能在其他芯片中看到类似的理念,但A100的选择和实现总是更通用,更“软”,更巧妙。
我理解的数据特点:
- activation只读,不会更改。流量大。
- 权重复用度高,推理时const只读,训练时需要读和写。
- AI数据里冗余数据多,本质上是稀疏的,特别是Relu之后activation,压缩权重,Embedding输入数据,broadcast数据等。
我理解的AI计算特点(暂时还没有统计数据来支持):
- 一个网络模型由很多的小Kernel组成。
- 单个Kernel宽而不深,也就是thread多,但kernel不长,指令数不多。
- Tensor Core (MMA)和Compute core的并行优化可能比较难做(库与编译器)
看看A100的存储系统改进,我关注了以下这些:
- 新的异步Copy指令,从global memory直接load到shared memory。
- 超大L2C,partitioned,use it smarter。
- 新的指令做L2 cache residency control。
- Compute Data Compression: 4x DRAM and L2 bandwidth, 2x L2 capacity. (不同于Structured Sparsity)
- Combined L1 data cache and shared memory (其实是Volta架构开始就有的)
Asynchronous copy
我理解为这是专为DNN Kernel里的Input/Activation数据类似的数据流优化。
- 因为这类数据,读自Global Memory,大都一次使用,不需要到RF,用完不需写回。因此没必要走复杂的L1/RF。而把RF/L1的带宽留给weigh和temp变量。
- 另外,这是异步操作,和SM里其他操作可并发,从而可以隐藏较长的copy cycles。使用新的shared-memory-based barrier来做同步。
- 同时,需要新的编程模型(CUDA)支持和编译器支持。
L2 cache residency control
首先,L2的设计很复杂,整理一下思路,根据新的特性出发来思考:
- 大幅度增长的L2 size (40MB),
- 首先,主要是为了大量的权重数据重用。
- 其次,MIG architecture的引入,需要将L2分成最多7份,也需要更大的size
- 大的L2,开始分区使用,类似于我们的LLC+L2的设计。
- 分区以后,每个GPC直连L2了,带宽增加(细节不懂。。。)
- 每个区应该也继续像以前一样分类,这样就是看,L2像是被做了两维的拆分,不知道这个是不是就是NV提到的new partitioned crossbar structure。
- 但整个L2Cache仍然保持“Shared”属性,HW cache-coherence,以及CUDA编程模型。
AI应用中,L2C应该主要优化了权重的复用,根据模型的大小,可以有不同级别的重用粒度,这个思路和之前在V1.5里提到的根据权重大小不同而产生的不同工作模式
- 权重全部在L2C,就像V1一样,整个模型权重只用装载一次到L2C。
- 权重需要分段轮流装载L2C,
- 装载一部分权重后,把相关的计算一个一个batch做完。然后换下一段权重,继续做。
- 这个时候,多batch的工作模式比较有优势。能公用更多的权重。
- 使用double buffer (ping-pong buffer)的模式,可以让计算和权重装载并行。
说到这里,大概就知道为什么需要L2 cache residency control。就是用来分段装载,实现ping-pong buffer的。
同样,也需要指令和CUDA编程模型的支持。
Compute Data Compression
注意这不是Structured Sparsity,而是针对unstructured sparsity and other compressible data patterns.
我知道我们也在看相关的技术。只是现在还不知道A100是怎么做的,但至少证明了这条路是可行的。需要大家一起持续关注。
Combined L1 cache and shared memory
从Volta架构开始,这个改变就发生了。理由看起来也比较充分:越来越多的计算场景,对Shared Memory和Cache的需求有很大不同。很难有一个固定比例能满足大部分的场景。所以可以配置的Shared Memory和L1C应该能比较方便而显著地提高性能。不需要为了固定的Shared Memory/L1C而绞尽脑汁地调整程序和调试性能。
之前的讨论中邹老师提到,做过一些实验发现差别不大。我有一些猜测,可能是实验面不够广,我们可以做进一步的实验:
- 这个改变是在Volta架构里,随着Tensor core一起引入。TC计算里这种差别可能特别明显,比如
- 做Matmul/Conv等使用TC的Kernel,通常是Shared Memory要的比较多(绝大部分),而cache用的很少
- 如果是LSTM的复杂Kernel,则会对Cache要求比较多。
- 越来越多的通用/AI计算场景,
整体而言,这个灵活性是有价值的。非常有利用调整和控制Kernel访存的性能和最终性能。
cache系统总结
首先是提供能力:BW/Capacity/利用率
- 直接提高带宽
- Capacity
不止单纯的大小,还包括动态分配等方式
- DRAM BW
1.7x BW不足,通过6.7x L2 + Residency Control弥补
其次是减少需求:
- Sparsity
- Compression
计算效率
Task Graph Acceleration
这应该是对整体性能(整个模型甚至多个模型)非常意义的一个特性。针对的是“一个网络模型由很多的小Kernel组成”这个特征。
这个思路---把多个Tasks,memory copies(uploading/downloading)做成一个图(Graph,Directed Acyclic Graph),加上Dependency关系,以前都是在软件端实现的。比如:
- GPU应用领域,多个draw batches,相互间或有复杂的dependency,为做到高效地并行,避免不必要的bubble,最好能做到out-of-order的执行。我知道的软件栈,做过相关的优化处理:UMD建立有dependency关系的DAG,KMD根据DAG,dependency信息,以及硬件的执行状态,动态的调度draw batches。
- AI领域,DNN本身就是一个DAG。
- 在NPU V1架构上,这个问题不明显但也存在。不明显的原因是大部分连续的可支持的算子被合并成了一个执行Engine。但遇到大的Engine需要拆分的时候,还是需要会有这个问题的,所以我们还是在软件栈实现了基于DAG的推理引擎。
- 在GPGPU架构,加上训练功能,将会有很多很多小的Kernel了。因此特别需要这个优化。
因此存在CPU和GPU之间的来回调度。A100的创新是把这个移到了硬件上。去除了这个CPU-GPU调度代价。
CUDA Graph
CUDA 10开始支持的一个特性,是Task Graph Acceleration的基础。首先把多个Kernel一起提交(submit)到GPU。减少kernel提交和同步的时间。不过submit之后的kernel只能按照顺序执行。并行度比较差。
Task Graph Acceleration
Ampere架构支持以图的方式提交,并带有相互的依赖关系。因此GPU硬件可能更高效的调度kernel并行。