H20算力秘密: GPU算力评估-中
一、从H20谈起
NVIDIA在国内主要销售H20。这个卡TFLOPS自然符合相应的限制要求,但为何(NV宣称)能平替A800/A100呢?
看下表:
H20的FP16 TFLOPS是148,低于A800 FP16的312。但H20的FP8 TFLOPS是296,而A800并没有设计FP8。
此外,H20的显存是96G,而A800的是80G。H20的NVL速率是A800的两倍多、显存带宽也接近A800的两倍。
根据上一篇文章中的方法:
H20的FP16峰值计算能力为148Tflops,而NVLink的双向带宽为900GB/s。对于 H20 的情况下,张量并行 GPU 数量最多可以达到 148 个,通信不会成为瓶颈。
A800的FP16峰值计算能力为312Tflops,而NVLink的双向带宽为400GB/s。对于 H20 的情况下,张量并行 GPU 数量最多可以达到 31个,通信不会成为瓶颈。
但我们知道,一个H20服务器最多是8个卡。也就是说,相对于算力来说,NVL通讯量剩余。那么,训练中什么数值会影响NVL通讯量呢?影响大小分别为:MicroBS, gradient accumulate >TP>Global BS。也就是说,我们在训练的时候。如果NVL不是限制,可以增加MicroBS,MicroBS越大,训练步数越少,相对训练速度越快。但有个前提是不能出现内存溢出。而H20的显存比A800多20%。也就是说,在相同的训练环境下,当GPU算力没有到上限前,增加BS,A800会先比H20出现OOM。但前面也提到了A800的FP16算力是H20的两倍多。但如果H20用FP8和A800的FP16比呢?是不是两者GPU算力就接近了,而H20的内存速率和NVL更高,BS就能更大些。
关于FP8训练和推理的精度问题,参考此前文章:
二、训练和推理对AI算力的要求
训练过程
训练过程不仅需要存储模型参数,还需要存储梯度、优化器状态和正向传播每一层的中间状态(activation)。这些额外的存储需求使得训练过程对内存的需求量更大。此外,训练过程包括正向传播和反向传播两个阶段:
-
正向传播(Forward Pass):
-
计算需求:正向传播需要将输入数据通过神经网络层层传递,计算每一层的输出。这部分计算量较大,但通常不会成为主要的瓶颈。
-
存储需求:需要存储每一层的中间状态(activation),以便在反向传播时使用。
-
-
反向传播(Backward Pass):
-
计算需求:反向传播是训练过程中最关键的部分,需要计算损失函数相对于每个参数的梯度,并更新参数。这涉及大量的矩阵运算和微分操作,是计算密集型任务。
-
存储需求:需要存储梯度和优化器状态,这些存储需求通常比模型参数本身更大。
-
-
通信需求:
-
梯度同步:在数据并行的情况下,每个 GPU 计算自己处理的数据批次的梯度,然后需要在所有 GPU 之间同步这些梯度。这会导致大量的通信需求。
-
模型并行:在模型并行的情况下,正向传播和反向传播都需要在 GPU 之间传递数据和梯度,这增加了通信需求。
综上所述,训练过程更依赖于算力,因为反向传播中的梯度计算和参数更新是计算密集型任务。同时,训练过程也需要一定的通信带宽来同步梯度,但整体上算力需求更为关键。
-
推理过程
推理过程主要涉及正向传播,不需要进行反向传播,因此不需要计算梯度和更新参数。推理过程的特点如下:
-
正向传播(Forward Pass):
-
计算需求:推理过程中主要依赖于正向传播的计算,这部分计算量较大,但相对于训练过程中的反向传播,算力需求相对较低。
-
存储需求:推理过程中不需要存储梯度和优化器状态,只需要存储正向传播的中间状态(activation)。由于推理任务中的各个输入数据之间没有关系,正向传播每一层的中间状态也不需要保存下来,因此存储需求较低。
-
-
通信需求:
-
模型并行:在模型并行的情况下,推理过程中每一层的输出需要传递给下一层所在的 GPU。这会导致大量的通信需求。
-
数据并行:在数据并行的情况下,每个 GPU 处理不同的数据批次,通信需求相对较低,只在需要汇总结果时才需要通信。
-
-
KV Cache:
-
存储需求:为了提高推理效率,可以使用 KV Cache 将前面每一个 token 的 K 和 V 矩阵缓存起来,生成下一个 token 时不再需要重新计算。这是一个拿内存换计算的策略,增加了存储需求,但显著减少了计算量。
-
计算需求:KV Cache 可以显著减少计算量,因为每存储 1 个字节,可以节约大量的计算。
综上所述,推理过程更依赖于通信带宽,特别是在模型并行的情况下,因为需要在 GPU 之间频繁传递数据。同时,推理过程的计算需求相对较低,但需要一定的存储容量来缓存中间结果和 KV Cache。
-
总结
-
训练过程:更依赖于算力,因为反向传播中的梯度计算和参数更新是计算密集型任务。同时,训练过程也需要一定的通信带宽来同步梯度,但整体上算力需求更为关键。
-
推理过程:更依赖于通信带宽,特别是在模型并行的情况下,因为需要在 GPU 之间频繁传递数据。推理过程的计算需求相对较低,但需要一定的存储容量来缓存中间结果和 KV Cache。
因此在训练中,H20使用FP8而A800使用FP16的情况下,H20应该和A800的训练性能持平起码不会低。但如果是推理(H20也支持8位推理),由于H20有更大的显存、更高的内存带宽和NVL带宽,相信H20的性价比要优于A800。
三、KV cache都和什么有关
如前文所述,推理中会用到KV cache,那么KV cache都和什么有关?
KV Cache 是一种用于优化生成模型推理过程的技术。对于每个输入的 prompt,在计算第一个 token 的输出时,每个 token 的 attention 都需要从头计算。然而,在后续 token 的生成过程中,需要计算 self-attention,即输入 prompt 和前面生成的 token 的 attention。在这种情况下,需要用到前面每一个 token 的 Key(K)和 Value(V)矩阵。
由于每一层的参数矩阵是固定的,因此在生成新的 token 时,只有刚生成的那个 token 的 K 和 V 需要重新计算,而输入 prompt 和之前生成的 token 的 K 和 V 矩阵实际上是与上一轮相同的。因此,我们可以将每一层的 K 和 V 矩阵缓存起来,在生成下一个 token 时不再需要重新计算,这就是所谓的 KV Cache。需要注意的是,Query(Q)矩阵每次都不同,因此没有缓存的价值。
在训练过程中,选择性保存正向传播的中间状态(activations)是一种用计算换内存的策略,而 KV Cache 则是一种用内存换计算的策略。
KV Cache 的存储需求
KV Cache 的存储需求取决于以下几个因素:
-
层数(L):模型的层数。
-
token 数量(T):需要缓存的 token 数量。
-
批量大小(B):批量大小,即一次处理的样本数量。
-
嵌入维度(D):模型的嵌入维度。
-
数据类型大小(S):每个数值的存储大小(例如,float16 是 2 字节,float32 是 4 字节)。
-
模型的最大序列长度
每一层的每个 token 的 K 和 V 矩阵大小为嵌入维度,再乘上 token 数量和批量大小,就是这一层的 KV Cache 所需的存储容量。例如,在 LLaMA 2 70B 模型中,假设批量大小为 8,输入和输出的 token 数量达到了模型的极限 4096,80 层的 KV Cache 总共需要:
2 (K, V) * 80 * 8192 * 4096 * 8 * 2B = 80 GB。如果 batch size 更大,那么 KV Cache 占据的空间将超过参数本身占的 140 GB。
KV Cache 的计算节约
KV Cache 可以显著减少计算量。每一层计算 K 和 V 矩阵的计算量为:
每一层计算 K、V 矩阵一共需要 2 (K, V) * 2 (mult, add) * embedding size * embedding size = 4 * 8192 * 8192 这么多计算量,乘以之前输入过的 token 数量、层数和 batch size,就是 4096 * 80 * 8 * 4 * 8192 * 8192 = 640 Tflops。相当于每存储 1 个字节,节约了 16K 次计算
内存带宽的影响
计算 K 和 V 矩阵的过程是一个典型的内存密集型过程,需要加载每一层的 K 和 V 参数矩阵。如果不做任何缓存,假设 prompt 长度很短而输出长度接近 token 的最大长度 4096,到了最后一个 token 时,单是重复计算前面每个 token 的 K 和 V 矩阵,就需要读取内存:
4096 * 80 * 2 * 8192 * 8192 = 40T 次,每次 2 个字节,要知道 H100 的内存带宽只有 3.35 TB/s,4090 更是只有 1 TB/s,这单是最后一个 token 就得耗掉一张卡几十秒的时间来做重复计算。这样,token 的输出就会越来越慢,整个输出时间是输出长度平方级别的,根本没法用。通过使用 KV Cache,可以显著减少重复计算,提高推理效率。
关于序列长度对KV cache的影响,可以参照我此前文章。
TensorRT-LLM, PagedAttention与FlashAttention
四、长文本推理带来的挑战
长文本推理带来的挑战:
-
更大的 KV 缓存带来更大的内存访问需求
-
无法为生成阶段构建大型批处理。
-
生成请求延迟会受到上下文阶段的干扰更长的系统提示,大量冗余计算。
所以长文本推理优化主要需要解决KV cache带来的问题。
优化方法1:Streaming-LLM
-
滑动窗口注意力:只缓存最近的键值状态。当初始标记被驱逐时,模型会崩溃。
-
流式LLM:在滑动窗口注意力中添加“汇”标记,并通过移动位置而不是原始文本来实现。
图片中展示了不同层和头的注意力矩阵,以及密集注意力、窗口注意力和流式LLM的对比。还展示了流式LLM的注意力机制和性能图表,表明其在处理长输入时的性能表现。
优化方法2:Multi-Block-Mode
-
分割键/值:将键和值分割成更小的块。
-
并行计算:并行计算查询与每个分割块的注意力。
-
保存额外的标量:为每行和每个分割块保存一个额外的标量,用于计算注意力值的对数和指数。
-
计算实际输出:通过对所有分割块进行归约计算实际输出,使用对数和指数来调整每个分割块的贡献。
图中展示了将键和值分割成多个块,并行计算查询与每个分割块的注意力,最后通过对所有分割块进行归约来计算最终输出。
优化方法3:Inflight Batching
旨在优化大型语言模型的键值缓存(KV-Cache)。主要内容包括:
-
请求池:将请求分为两类:上下文阶段请求和生成阶段请求。不同颜色代表不同类型的请求。
-
批处理:将请求池中的请求进行批处理,形成一个二维矩阵。矩阵的行表示输入长度,列表示批处理大小。
-
移除输入填充:在批处理后,移除输入中的填充部分,以优化计算效率。
通过这种方式,可以更有效地利用键值缓存,提高大型语言模型的性能。
优化方法4:Chunked Context
-
传统IFB调度器:
-
单个标记需要与非常大的上下文输入一起批处理,导致生成延迟较大。
-
激活内存成本高。
-
用户体验受损。
-
-
启用分块上下文:
-
算术强度降低,导致GPU利用率降低。解决方案是通过分析选择合适的分块大小,以最大化模型的吞吐量。
-
注意力计算由于从先前分块的KV缓存中重复访问内存而变得复杂。
图中展示了启用分块上下文后,如何将上下文分成多个块进行批处理,以提高性能并降低延迟。
-
上下文分块增加了批处理上下文和生成阶段的可能性,从而提高了性能。
-
对于第一个标记存在多重固有问题,带来了额外的开销。
-
优化方法5:KV-Cache-Reuse
-
KV缓存缓冲池:在请求之间共享的KV缓存缓冲池。通过索引(Indices)来管理缓存的键和值。
-
新输入序列:新的输入序列被分成多个块(block1, block2, block3, block4, block5)。
-
查找现有KV缓存:通过输入ID查找现有的KV缓存。如果找到匹配的键,则可以重用缓存。
-
推理引擎:推理引擎使用KV缓存来加速推理过程。
-
插入新的KV缓存:如果没有找到匹配的键,则将新的KV缓存插入到缓冲池中,并设置索引。
通过这种机制,可以有效地重用KV缓存,从而提高推理过程的效率。
优化方法6:使用FP8量化模型
这张图片深入探讨了FP8 GEMM和FP8 FMHA在性能方面的优势。主要内容包括:
-
GPT-175B模型的性能分析:
-
对比了不同硬件配置下的上下文阶段和生成阶段的时间消耗。
-
重点关注了FP8和BFP16在不同批量大小下的性能差异。
-
-
FP8 GEMM的优势:
-
Hopper架构的FP8 GEMM在性能上优于Ampere架构的INT8 GEMM。
-
FP8 FMHA在性能上比FP16(BFP16)FMHA快2倍。
-
-
性能数据:
-
表格中列出了不同批量大小下,FP8和BFP16的FMHA性能对比。
-
数据显示,FP8在不同批量大小下的性能都优于BFP16。
总体而言,FP8在GEMM和FMHA方面的性能优势明显,特别是在大型模型的推理和训练中。
-
参考链接:https://zhuanlan.zhihu.com/p/655402388?utm_psn=1738966133057785856