大语言模型应用需要做算力测算,传统方法一般会通过性能测试做一个评估,但实际的情况比较复杂,客户大模型基础设施可能是异构和混配的,开发、测试和生产的模型也可能不同,同样的模型也可能有多种不同尺寸可以选择,还可以选择不同的量化版本,同时在推理阶段还有不同的优化策略,仅仅通过性能测试依然很难获得不同情况下准确的算力需求。
大模型的训练和推理在算力、显存和通信三个方向都有很大的压力,常见的算力计算公式会采用显卡算力除以输入输出的token数需要的算力数,然后再除以单卡算力来测算需要的显卡数量。但实际上用于推理卡的显存一般都不大,常见规格是16G,24G和40G左右。如果需要部署参数规模在14B以上的大模型后,单张显卡可用的显存会明显下降,而可用显存会显著影响推理的吞吐量。因此往往大模型推理的性能常见的瓶颈在于显存。在实际生产环境中,需要多卡云化的部署,并行策略可以采用数据并行、张量行或流水线并行策略可以提高推理吞吐量,但也会额外增加调度和通信的开销,甚至部分场景下通信带宽也会成为性能瓶颈,消费级的显卡低带宽的弱点就会很明显,这部分内容不在本文讨论范围。
大模型的推理的显存占用主要有三部分构成:模型参数、KV Cache和其它(激活值、Buffer和显存碎片等),三部分KV Cache的显存占用会随着并发量和输入队列长度(即每个输入序列的token数)而显著变化。因此,KV Cache 的显存占用可以随这些因素的不同而波动。常见三者占用显存比率关系:模型参数占比:40% ~ 60%;KV Cache(键值缓存)占比:20% ~ 50%;其它占比:10% ~ 20%。
本文探讨了单卡情况下显卡显存占用的原理和测算问题,总结下大模型推理的显存占用的原理及部分实践的经验,提供显存评估的简易公式,推荐部分在线测算工具,也有助于理解在复杂场景下性能评估问题。
介绍下Transform架构和模型参数构成,对显存使用原理有一个基本认识,并有助于理解后续的显存计算原理。
包括GPT在内,目前主流的大语言模型都是基于Transformer的Decoder-only架构,架构的结构如下图所示:
任何一个客户请求进入大模型后,其解码过程都需要经过上图所示架构的流程步骤,解码的过程可以理解为两个主要过程:
输入处理(也称为预填充):在预填充阶段,LLM 处理输入令牌以计算中间状态(键和值),这些状态用于生成“第一个”新令牌。每个新令牌都依赖于所有以前的令牌,但由于输入的完整范围是已知的,因此在高级别上,这是一个高度并行化的矩阵-矩阵操作。它有效地使 GPU 利用率饱和。
输出生成(也称为解码):在解码阶段,LLM 一次自动生成一个输出令牌,直到满足停止条件。每个序列输出的Token都需要知道所有先前迭代的输出状态(键和值)。这就像一个矩阵-向量操作,与预填充阶段相比,它没有充分利用 GPU 计算能力。数据(权重、键、值、激活)从内存传输到 GPU 的速度决定了延迟,而不是计算实际发生的速度。
数据类型
根据IEEE 754的规范,float 和 double 类型的数据在内存中以二进制方式存储,由三部分组成,以单精度浮点数为例,其表示方法如下所示,占用4个字节,32 bits:
符号位 S(Sign): 0 代表正数,1 代表负数
指数位 E(Exponent):用于存储科学计数法中的指数部分,决定了数据的范围
尾数位 M(Mantissa):用于存储尾数(小数)部分,决定了数据的精度
模型的参数构成
以GPT-3 175B为例,定义部分符号名称方便表示后续的参数计算公式,记模型的层数为L,隐藏层维度为h,注意力头数为a。词表大小为V,训练数据的批次大小为b,序列长度为s。
【GPT-3 175B】:L = 96 ; h = 12,288 ; a = 96 ; s = 2,048 ; V = 50,257
输入输出层
输入输出都需要词向量矩阵,输入的词矩阵包括词向量表和位置编码表,参数量计算如下:
输入参数量 = Vd = 50257 * 12288 = 617,558,016
输出参数量 = Vd = 输入参数量
合计参数量 = 2Vd = 1,235,116,032
解码层
GPT3的整个Decoder的结构如图一所示,分为自注意力层,前馈网络层,每个子层后都接有一个规范化层和一个残差连接。下面就每层的参数计算做一个分解说明:
- 多头注意力层
注意力层的模型参数主要有 QKV 的权重矩阵
和偏置,输出权重矩阵 和偏置,4个权重矩阵的形状为 [h,h] ,4个偏置的形状为 [h] 。注意力层的参数量为 。 ➡️QKV矩阵:
QKV三个矩阵形状相同,以Query矩阵为例,其参数计算方法如下:
QKV合计 =
➡️多头汇总层矩阵:
➡️合计参数量:
= = =
- 前馈网络层
前馈网络层(FFN) 是个全连接层,GPT3的架构中包含两层,第一层这个线性变换由参数(W1)和(b1)参数化,然后是一个激活函数,接着是另一个由参数(W2)和(b2)参数化的线性变换。
根据前馈网络层的结构,参数的计算过程如下:
➡️参数尺寸:
矩阵
的尺寸是,其中 ,这里的4是前馈网络的模型内部维度扩大的倍数。在LLaMA计算中,FFN中第一层的维度没有采用4倍,而是近似8/3大小。 矩阵
的尺寸是 。 偏置项
和 的尺寸分别 是 和 。 每个矩阵
和 贡献 个参数。 偏置项
和 贡献 个参数。 因此,每一层的总参数数量是
。
➡️参数数量:
总参数=96层 x 每层FFN参数大小=115,970,015,232
模型参数合计:
分层 | 参数量 |
输入层 | 617,558,016 |
自注意力层 | 57,986,777,088 |
前馈网络 | 115,970,015,232 |
输出层 | 617,558,016 |
175,191,908,352 |
这个计算结果跟GPT3对外宣称的175B的参数量基本吻合。但采用类似公式对最新的模型例如QWen2.5系列模型做参数测算,会发现有10-20%的误差,这可能跟新模型结构变化有关。不过以上计算方法作为Transformer架构模型的基础知识,依然有助于对大模型参数构成的理解。
在推理阶段模型占用的显存要远小于训练阶段,执行推理显存需要用于加载模型权重,存储 KV 缓存的资源,以及激活参数和其它(激活值、Buffer和显存碎片等),其它部分的占比不多,可以按模型参数占用的内存量的10%~20%做测算。
总推理内存 = 模型内存 + KV 缓存 + 其它内存 。
快速计算公式
不考虑KVCache,推理需要的显存可以用模型参数占用内存加额外开销(通常控制在总内存的20%以内),可以下面的快速计算公式:
公式中的符号含义如下:
符号 | 描述 |
M | 需要的GPU内存,以千兆字节(Gigabyte)表示 |
P | 模型中的参数数量。例如,一个7B模型有70亿个参数 |
4B | 每个参数使用的字节数,例如4字节 |
32 | 4字节中需要多少bits,例如有32位 |
Q | 加载模型时应使用的位数。例如,16位、8位或4位 |
1.2 | 表示在GPU内存中加载额外内容的20%额外开销 |
以Llama2 70B模型为例
采用16位精度数据类型,用上述的公式试算一下,显存需求如下:
采用16位精度来部署Llama2 70B模型需要168G显存,单块A100 80GB的GPU不够,需要两块A100 80GB的GPU来部署。
以Llama2 70B int4量化模型为例
量化是一种减少模型内存占用的方法,它通过将模型参数的精度从浮点数降低到更低比特位的表示形式(例如8位整数)来实现这一点。这一过程显著降低了内存和计算需求,使得模型能够更高效地部署,尤其是在资源有限的设备上。让我们以4位量化的Llama 270B为例:
可以发现显存需求降低了4倍,这样采用3个T4 16G或2个L4 24GB的GPU上运行的Llama2 70B的量化模型了。
在线计算工具
Model Memory Calculator
这是 huggingface的accelerate开发库中提供的一个工具。它根据模型的元数据来对大模型所需要的内存进行模拟计算。以“Qwen2.5-14B”模型测算为例:
Memory usage for 'Qwen/Qwen2.5-14B-Instruct'
GPU-poor
GPU-poor可以基于你给出的配置,直接估算出模型内存占用量以“Qwen2.5-14B”模型测算为例:
KV-Cache占用
解码阶段常见优化是KV-Cache,其本质上是用空间换时间,也是推理必备功能。解码阶段在每个时间步长生成一个标记,但每个标记都取决于所有先前标记的键和值张量,其原理如下图所示:
存储的Key、Value矩阵会额外占用内存,每个Token的存储占用公式如下:
第一个因子 2 表示 K 和 V两个矩阵。通常(num_heads * dim_head) 的值与Transformer的 hidden_size(或模型的维度 d_model)相同。在输入批次中,输入序列中的每个令牌都需要此内存大小。假设采用半精度,KV 缓存的总大小由以下公式给出。
例如:对于 16 位精度的 Llama 2 7B 模型,批处理大小为 1,sequence Length= 4096 ,Number of Layers = 32,Hidden Size = 4096,则 KV 缓存的大小将为
如果Batch Size = 64,计算得到:
可以看到,随着 batch size和长度的增大,KV cache占用的显存需求随着批量大小和序列长度线性增长,甚至会超过模型本身,它限制了可服务的吞吐量,并对长上下文输入提出了挑战。
大模型的技术依然在高速发展,不断有新的优化技术出现,目前针对KV cache优化有多种方法,例如:RadixAttention 、FlashAttention、PagedAttention 、MQA和GQA等。新的推理算法和框架在相同资源情况下的性能会有明显提升。
根据Qwen官方文档性能测试报告(NVIDIA A100 80GB):
Qwen模型系列参数如下:
以6144的Token输入,BF16精度为例,通过公式计算如下:
模型参数:7.61B:显存占用 = 15.2GB
KV-Cache:显存占用
其它:
显存占用 = (15.2+2.3)* 0.2 = 3.5GB
合计 ,21GB左右,同官方测试数据20.26GB类似,在5%误差范围内。
合计显存
使用LLMPerf在一张RTX 4090显卡上测试,模型显存占用约为8.1G 。测试中设置了5个并发请求,平均每个请求输入token数为8000,模型模型层数64层,Hidden-size为5120,KV Cache占内存量约为13GB,KV Cache通过量化设置可以进一步减少到6G左右。下面是具体的测试结果:
输入和输出令牌数:
mean_input_tokens:平均每个请求的输入令牌数为8000。
stddev_input_tokens:输入令牌数的标准差为10。
mean_output_tokens:平均每个请求的输出令牌数为128。
stddev_output_tokens:输出令牌数的标准差为10。
并发请求数:
num_concurrent_requests:测试中设置了5个并发请求。
令牌间延迟(Inter-token Latency):
这个指标衡量的是模型生成每个令牌所需的时间。
quantiles:给出了不同百分位数(25%,50%,75%,90%,95%,99%)的延迟时间,可以帮助我们了解在不同情况下的延迟表现。
"results_inter_token_latency_s_quantiles_p75":0.048172636413348326...
mean:平均延迟时间为0.038秒。
min:最小延迟时间为0.021秒。
max:最大延迟时间为0.079秒。
stddev:延迟时间的标准差为0.016秒。
思考时间(TTFT):
这个指标衡量的是模型开始生成第一个令牌之前的时间。
quantiles:给出了不同百分位数(25%,50%,75%,90%,95%,99%)的TTFT时间。
"results_ttft_s_quantiles_p75": 9.914196312507556...
mean:平均TTFT时间为7.202秒。
min:最小TTFT时间为1.978秒。
max:最大TTFT时间为13.790秒。
stddev:TTFT时间的标准差为3.595秒。
测试结论:采用单卡4090,显存控制在99%使用率之内,采用5个batch,输入8k左右,输出128的设置,首词输出在5s左右,后续生成平均是30 tokens/s。
参考文献:
【1】大语言模型推理性能优化综述
【2】Attention Is All You Need
【3】 The Illustrated Transformer
【4】Language Models are Few-Shot Learners
【5】The ChatGPT Models Family
【6】Understanding VRAM and how Much Your LLM Needs
【7】Mastering LLM Techniques: Inference Optimization
【8】Model Memory Calculator
【9】GPU Poor