写点什么

大模型推理显存和计算量估计方法

作者:AI布道Mr.Jin
  • 2025-06-22
    浙江
  • 本文字数:3928 字

    阅读完需:约 13 分钟

最近做吞吐量调试涉及到输入 batch_size 的设置,为了把算力和显存用起来,同时不触发 out of memory,需要提前估计大模型推理过程中的显存占用,我参考了 MindIE 官网的这个文档完成了估计:https://www.hiascend.com/document/detail/zh/mindie/20RC2/mindieservice/servicedev/mindie_service0105.html

显存估计

大模型推理的过程中,显存主要用来存储 kvcache。kvcache 的大小和 token 的数量成正比,我们首先来看一下单个 token 的 kvcache 怎么计算。假设单 token 输入经过 embedding 层之后的维度是 hidden_size,那么接下来就要和 k 权重矩阵和 v 权重矩阵相乘,kv 的权重矩阵 shape 是[hidden_size, hidden_size],所以计算得到的 k 和 v 的维度是 hidden_size。需要注意的是,虽然这里 kv 矩阵只有 1 个,但实际上现在大部分模型都采用了多头注意力,注意力头的数量是 num_attention_heads,单个注意力头的权重矩阵是[hidden_size, hidden_size/num_attention_heads]。此外,如果采用分组 kv 头,也就是模型的 config.json 文件中包含 num_key_value_heads 参数,那么 kv 的权重矩阵 shape 就是[hidden_size, (hidden_size/num_attention_heads)*num_key_value_heads],这样的话要缓存的 kv 的维度就是(hidden_size/num_attention_heads)*num_key_value_heads。由于大模型一般包含多层 transformer,所以还需要乘以层数。总的来说,单个 token 占用的 kvcache 显存大小计算公式如下:


import numpy as np# take qwen2.5-7B as examplehidden_size = 3584num_attention_heads = 28num_hidden_layers = 28num_key_value_heads = 4  # if not grouped kv cache, num_key_value_heads equals num_attention_headskvcache_dtype_byte = 2  # bf16/fp16 -> 2, int8 -> 1cache_num = 2  # k cache and v cache
one_token_cache = hidden_size / num_attention_heads * num_key_value_heads * num_hidden_layers * kvcache_dtype_byte * cache_numprint(f"one_token_cache is: {one_token_cache} byte.")
复制代码


计算了单个 token 的 kvcache 显存大小后,我们就可以计算整个序列所占的显存大小了:


input_length = 1024output_length = 1024batch_size = 16allocate_mem = one_token_cache * (input_length + output_length) * batch_size / (1024*1024*1024) # GBprint(f"allocate memory should be larger than {allocate_mem} G.")
复制代码


还可以估计所能支持的最大 batch_size:


# estimate max batch_size for qwen2.5-7Bmodel_size = 7  # Btotal_mem = 64  # 64G for each npumem_coefficient = 0.8mem_for_kvcache = (total_mem - model_size*kvcache_dtype_byte)*mem_coefficient*1024*1024*1024  # bytekvcache_block_size = 128max_block_num = np.floor(mem_for_kvcache/(kvcache_block_size*one_token_cache))print("max_block_num: ", max_block_num)one_sequence_block_num = np.ceil((input_length + output_length) / kvcache_block_size)max_batch_Size = np.floor(max_block_num / one_sequence_block_num)print("max_batch_Size: ", max_batch_Size)
复制代码


在估计最大 batch_size 的时候,我们先根据显卡的最大容量(64G)和模型大小估计了可用于缓存 kvcahe 的空间 mem_for_kvcache,然后估算了可以分配多少个 kvcache_block。接着计算一个 sequence 所需要的 kvcache_block 数量,最后用总的 kvcache_block 数量除以一个 sequence 所需要的 kvcache_block 数量,得到的就是支持的最大 batch 数量。


上面的代码运行结果如下:


one_token_cache is: 57344.0 byte.allocate memory should be larger than 1.75 G.max_block_num:  5851.0max_batch_Size:  365.0
复制代码


为了验证理论分析是否正确,我们用 MindIE 跑一下 qwen2.5-7B 模型:


bash run.sh pa_bf16 performance [[1024,1024],[256,256]] 16 qwen /home/jinxiulang/qwen2.5/Qwen2.5_7B_Instruct 1
复制代码


运行日志截图如下:



可以看到,日志中包含了“kv cache will allocate 1.75GB memory”,和我们上面估算的是一致的。

计算量估计

为了估算推理执行耗时,还需要估计模型推理消耗的计算量,然后结合芯片算力估计时延。


Transformer 模型的计算量主要来自自注意力机制、前馈网络(FFN)和最后的 lm_head 层。假设模型参数如下:


  • L:Transformer 层数。

  • H:隐藏层大小(隐藏维度)。

  • I:FFN 中间层大小(通常 I>H)。

  • V:词表大小。

  • S:prefill 输入序列长度。

  • T:序列总长度(prefill+生成 token)。

Prefill 计算量

​ 计算每层 Transformer 的 FLOPs:


​ 首先计算自注意力层。第一步是求 q、k、v,计算公式是 input*weight,input 的 shape 是[S, H],weight 的 shape 是[H, H],所以 q/k/v 要做 SH 次向量内积,每次向量内积要做 H 次乘法和(H-1)次加法,近似于 2H,所以 q、k、v 所有的计算量是 3*SH*2H=6SH^2。需要注意的是,如果采用的是分组 kvcache,那么计算 q、k、v 的时候,H 要换成(H/num_heads)*num_kv_heads,但是在估算的时候可以近似为 H;然后是 Q*K(实际上是 Q 乘以 K 的转置),输入 shape 是[S, H]和[H, S],计算量是 2S^2H;接着是 Q*K*V,输入 shape 是[S, S]和[S, H],计算量是 2S^2H;最后是输出投影,输入 shape 是[S, H]和[H, H],计算量是 2SH^2;所以自注意力层的计算量是 8SH^2+4S^2H。


​ 然后计算 FFN 层的计算量,FFN 层包含一个升维层、一个 gate 层和一个降维层,计算量分别为 2SHI、2SHI、2SIH,所以总计算量为 6SHI。


​ 所以每个 transformer 层的计算量为 8SH^2+4S^2H+6SHI。再加上最后的 lm_head,prefill 的计算量为 L(8SH^2+4S^2H+6SHI)+2SHV。

Decode 计算量

​ 和 prefill 相比,decode 的主要变化是输入序列长度为 1。


​ 首先计算自注意力层。计算 qkv 的计算量是 6H^2。计算 q*k 的时候,由于要把缓存的 k 也加上,假设当前序列长度是 T,那么输入 shape 是[1, H]和[H, T],所以计算量是 2HT;接着计算 qkv,输入 shape 是[1,T]和[T, H],计算量是 2HT。所以自注意力层的计算量是 8H^2+4HT。


​ 然后计算 FFN 层的计算量,参考 prefill 的过程,可知是 6HI。


​ 所以每个 transformer 层的计算量为 8H^2+4HT+6HI。再加上最后的 lm_head,decode 的计算量为 L(8H^2+4HT+6HI)+2HV。需要注意的是,这里面的 T 是变化的,如果要计算 K 个 decode 过程的平均耗时,可以取 T=(S+S+K)/2 进行估计。

实验验证

为了验证我们的理论公式,我们继续基于 qwen2.5-7B 进行验证,还是运行:


bash run.sh pa_bf16 performance [[1024,1024],[256,256]] 16 qwen /home/jinxiulang/qwen2.5/Qwen2.5_7B_Instruct 1
复制代码


输出结果部分截图如下:



在输入/输出取 1024/1024,batch_size=16 的情况下,首 token 时延 1088.62ms,decode 平均时延 18.77ms。


我们把理论计算公式用 python 实现:


# estimate calculation amount for qwen2.5-7B# prefillL = num_hidden_layersS = input_lengthH= hidden_sizeI = 18944V = 152064attention_cal_prefill = L*(8*S*H*H + 4*S*S*H)forward_cal_prefill = L*6*S*H*Ihead_cal_prefill = 2*S*H*Vtotal_cal_prefill = (attention_cal_prefill + forward_cal_prefill + head_cal_prefill)*batch_sizeprint(f"Total calculation amount for prefill is {total_cal_prefill:.3e}, the percentages of attention layer,"      f" forward layer and head layer are: {attention_cal_prefill/total_cal_prefill}, {forward_cal_prefill/total_cal_prefill}, {head_cal_prefill/total_cal_prefill}")
# decodeT = input_length + output_length / 2attention_cal_decode = L*(8*H*H + 4*H*T)forward_cal_decode = L*6*H*Ihead_cal_decode = 2*H*Vtotal_cal_decode = (attention_cal_decode + forward_cal_decode + head_cal_decode)*batch_sizeprint(f"Total calculation amount for averaged decode is {total_cal_decode:.3e}, the percentages of attention layer," f" forward layer and head layer are: {attention_cal_decode/total_cal_decode}, {forward_cal_decode/total_cal_decode}, {head_cal_decode/total_cal_decode}")
npu_cal_ability = 300*1e+12
print(f"The calculation time for prefill and decode are: {(total_cal_prefill / npu_cal_ability)}, {total_cal_decode / npu_cal_ability}")
复制代码


注意,代码中的 npu_cal_ability 是我们使用的 npu 单卡算力,300T Flops 左右。


输出结果如下:


Total calculation amount for prefill is 2.586e+14, the percentages of attention layer, forward layer and head layer are: 0.20832364566379913, 0.7226226458963032, 0.06905370843989769Total calculation amount for averaged decode is 2.558e+11, the percentages of attention layer, forward layer and head layer are: 0.21849896717925177, 0.7133348634381456, 0.06816616938260271The calculation time for prefill and decode are: 0.8620572025378134, 0.000852813851306666
复制代码


可以看到,prefill 的预估时间为 862ms,和实测的 1088.62ms 比较接近,但是增量平均时延为 0.85ms,远小于实测的 18.77ms。主要有以下原因:


1,AI 芯片的算力参数为峰值算力,一般情况下 AI 芯片的算力利用率在 60%左右;


2,AI 芯片的算力大部分来自矩阵乘法单元,全量计算都是大矩阵运算 GEMM,可以充分利用 AI Core 的能力,但是增量计算都是小矩阵运算(特别是 batch_size=1 的时候,退化为向量矩阵运算 GEMV),导致算力利用率很低;


3,token 时延除了包含计算时间,还有内存搬运时间、软件栈之间的数据传输时间等等,对于 decode 这种运算时间短的场景,其他环节的时延会占很大的比例。


此外,对于分布式推理场景,不能仅凭计算量来估计时延,因为通信算子的耗时往往会较大,比如 MOE 结构模型中的 alltoall 通信在推理过程中可能占总时延的 30%。

用户头像

还未添加个人签名 2020-11-13 加入

还未添加个人简介

评论

发布
暂无评论
大模型推理显存和计算量估计方法_AI布道Mr.Jin_InfoQ写作社区