大模型从零开始——模型参数量计算

文章目录

一、LLaMA2参数量计算

二、大模型参数解析

        2.1. 模型下载并加载

        2.2. 打印模型的所有参数

        2.3. 打印参数的形状和名称

        2.4. 打印模型的配置信息 

        2.5. 打印模型总参数量和大小

三、Transformer模型参数量估算


本篇我们将介绍如何计算基于Transformer架构的大语言模型的参数数量。由于当前主流的大模型普遍采用因果解码器(CausalDecoder)架构,因此下面以LLaMA2模型为范例,深入剖析其参数数量计算方式。对于其他模型,其参数量计算算法可参照此方法计算。

一、LLaMA2参数量计算

大模型一般都会将参数量命名进模型中,如 llama2:7B、glm4:9B、qwen2.5:32B。参数的7B、9B是指模型中可训练参数的数量。这里的“B”表示10亿(Billion),即10^9。因此,7B表示70亿个可训练参数,9B表示90亿个可训练参数。

首先,假设词表大小为 V,模型包含 L 层解码器,中间状态的维度大小为 H,前馈网络层的中间状态维度大小为 H′。

  • 输入嵌入层。首先,输入嵌入层( E \epsilon R^{V \times H} )将词表中的每个单词映射到一个 H 维的向量,因此输入编码层有 V × H 个参数。

  • 多头注意力层。传统的注意力机制部分包含查询( W^Q \epsilon R^{H\times H} )、键( W^K \epsilon R^{H\times H})和值( W^V \epsilon R^{H\times H} )的线性变换矩阵,每个变换矩阵都包含 H^2 个参数,所以这部分需要 3 × H^2 个参数。同时还需要一个额外的线性变换来将多头注意力机制的输出拼接后映射成最终输出( W^O \epsilon R^{H\times H}),这又需要 H^2 个参数。因此,多头注意力层总共需要 4 × H^2 个参数。

  • 前馈网络层。LLaMA 的前馈网络层由三个线性变换组成,中间有一个非线性激活函数。前两个线性变换( W^U \epsilon R^{H\times {H}'} 和 W^G \epsilon R^{H\times {H}'})将输入从 H 维映射到 H′ 维,需要 2 × HH′ 个参数;最后一个线性变换(W^D \epsilon R^{H\times {H}'})将输出从H′维映射回 H维,需要 H′ 个参数。因此,前馈网络层总共需要 3 × HH′ 个参数。

  • 归一化层。每一层解码器还包含两个 RMSNorm 操作,分别用于对多头注意力层和前馈网络层的输入进行归一化处理,共需要 2 × H个参数。此外,最后一层的输出也需要进行归一化处理,这又需要额外的 H 个参数。

  • 输出层。最后,LLaMA 的输出层包含一个线性变换(W^L \epsilon R^{H \times V}),将解码器的输出映射到词表大小 V 的维度上,使用 softmax 归一化后预测下一个单词的概率分布。这个线性变换需要 V × H 个参数。

综上所述,累积输入嵌入层、输出层和 L 层解码器每层的多头注意力层、前馈网络层和归一化层,LLaMA2 模型的参数量计算公式为:

Total\_Parameters = 2V \times H + H + L \times (4H^2 + 3H{H}'+ 2H)

LLaMA2 (7B) 为例计算其参数:

共32个Block,一个Block的参数情况:

词表Token数为32K。即给定 V = 32000, L= 32, H= 4096, H′ = 11008,将这些值代入上述公式中:

参数量 = 2 × 32000 × 4096 + 4096 + 32 × (4 × 4096^2 + 3 × 4096 × 11008 + 2 × 4096)= 6,738,415,616

计算得到的参数量与 LLaMA2 (7B) 模型的实际参数量完全一致。详见:https://ollama.com/library/llama2/blobs/8934d96d3f08

二、大模型参数解析

下面我们以Llama-2-7b-chat-hf为例,来实际打印模型的参数量看看。

2.1. 模型下载并加载

git clone https://www.modelscope.cn/shakechen/Llama-2-7b-chat-hf.git
pip install transformers
from transformers import AutoModelForCausalLM
 
# 指定模型路径
model_path = "./Llama-2-7b-chat-hf"
 
# 加载模型
hf_model = AutoModelForCausalLM.from_pretrained(model_path)
 
# 打印模型信息
print(hf_model)

从提供的输出中,我们可以看到Llama2模型的结构细节。这个模型是使用transformers库加载的。 

模型结构的关键点:

  • 嵌入层(Embedding):使用一个嵌入层将输入的词标(tokens)转换为固定大小的向量,这里的维度是4096,词汇表大小为32000,则参数数量为32,000 * 4,096
  • 解码器层(LlamaDecoderLayer):包含32个解码器层,每个层都具有以下组件:
    • 自注意力(LlamaSdpaAttention):一个自注意力机制,包括四个线性变换(q_proj, k_proj, v_proj, o_proj)以及一个旋转位置编码(rotary_emb)。
    • 多层感知机(LlamaMLP):包括一个门控投影(gate_proj),上升投影(up_proj),下降投影(down_proj),以及激活函数(act_fn),这里使用了SiLU(Sigmoid线性单元)。
    • 层归一化(LlamaRMSNorm):在输入和自注意力后使用RMS归一化,有利于模型训练的稳定性。
  • 输出层(Linear):最后,一个线性层将解码器的输出转换回词汇表空间,用于生成最终的输出词标,这里输出层的大小也是32000,与词汇表大小相同。

2.2. 打印模型的所有参数

from transformers import AutoModelForCausalLM
 
# 指定模型路径
model_path = "./Llama-2-7b-chat-hf"
 
# 加载模型
hf_model = AutoModelForCausalLM.from_pretrained(model_path)
 
for param_tensor in hf_model.parameters():
    print(param_tensor)

2.3. 打印参数的形状和名称

from transformers import AutoModelForCausalLM
 # 指定模型路径
model_path = "./Llama-2-7b-chat-hf"
 
# 加载模型
hf_model = AutoModelForCausalLM.from_pretrained(model_path)
 
for name, param in hf_model.named_parameters():
    print(f"{name}: {param.size()}")
Loading checkpoint shards: 100%|██████████████████████████████████████████████████| 2/2 [00:03<00:00,  1.79s/it]
model.embed_tokens.weight: torch.Size([32000, 4096])
model.layers.0.self_attn.q_proj.weight: torch.Size([4096, 4096])
model.layers.0.self_attn.k_proj.weight: torch.Size([4096, 4096])
model.layers.0.self_attn.v_proj.weight: torch.Size([4096, 4096])
model.layers.0.self_attn.o_proj.weight: torch.Size([4096, 4096])
model.layers.0.mlp.gate_proj.weight: torch.Size([11008, 4096])
model.layers.0.mlp.up_proj.weight: torch.Size([11008, 4096])
model.layers.0.mlp.down_proj.weight: torch.Size([4096, 11008])
model.layers.0.input_layernorm.weight: torch.Size([4096])
model.layers.0.post_attention_layernorm.weight: torch.Size([4096])
...
model.layers.31.self_attn.q_proj.weight: torch.Size([4096, 4096])
model.layers.31.self_attn.k_proj.weight: torch.Size([4096, 4096])
model.layers.31.self_attn.v_proj.weight: torch.Size([4096, 4096])
model.layers.31.self_attn.o_proj.weight: torch.Size([4096, 4096])
model.layers.31.mlp.gate_proj.weight: torch.Size([11008, 4096])
model.layers.31.mlp.up_proj.weight: torch.Size([11008, 4096])
model.layers.31.mlp.down_proj.weight: torch.Size([4096, 11008])
model.layers.31.input_layernorm.weight: torch.Size([4096])
model.layers.31.post_attention_layernorm.weight: torch.Size([4096])
model.norm.weight: torch.Size([4096])
lm_head.weight: torch.Size([32000, 4096])

2.4. 打印模型的配置信息 

from transformers import AutoModelForCausalLM
# 指定模型路径
model_path = "./Llama-2-7b-chat-hf"
 
# 加载模型
hf_model = AutoModelForCausalLM.from_pretrained(model_path)
 
print(hf_model.config)

从提供的LlamaConfig配置输出中,我们可以看到Llama2模型的关键配置参数。这些参数提供了模型架构的深入了解,下面是其中一些重要参数的解释:

  • _name_or_path: 指定了模型加载的路径或名称,这里是"./Llama-2-7b-chat-hf"
  • architectures: 包含了模型使用的架构类型,这里是"LlamaForCausalLM",表明这是一个因果语言模型。
  • attention_dropout: 注意力层中使用的dropout比率,这里设置为0.0,意味着没有应用dropout。
  • bos_token_ideos_token_id: 分别表示文本序列开始(BOS)和结束(EOS)的特殊标记的ID。
  • hidden_act: 隐藏层使用的激活函数,这里是"silu"(Sigmoid线性单元,也称为Swish激活函数)。
  • hidden_size: 隐藏层的大小,这里是4096,表明每个隐藏层的输出维度。
  • intermediate_size: 前馈网络(feed-forward network)层中间层的大小,这里是11008。
  • max_position_embeddings: 最大的位置嵌入维度,这里是4096,限定了模型能处理的最大序列长度。
  • model_type: 模型的类型,这里是"llama"
  • num_attention_heads: 注意力机制中使用的头数,这里是32。
  • num_hidden_layers: 隐藏层的数量,这里是32。
  • vocab_size: 词汇表的大小,这里是32000。
  • torch_dtype: 模型中使用的数据类型,这里是"float16",表明模型参数使用的是半精度浮点数,这有助于减少模型的内存占用,加快计算速度。

这个配置概览揭示了Llama2模型的一些关键特性,包括其深度、宽度和操作的技术细节。例如,32层的深度与4096的隐藏大小和32的注意力头数共同决定了模型的能力和复杂性,使其适合处理复杂的语言理解和生成任务。使用半精度浮点数(float16)是为了优化性能和资源使用,特别是在支持半精度计算的硬件上。

2.5. 打印模型总参数量和大小

from transformers import AutoModelForCausalLM

# 指定模型路径
model_path = "./Llama-2-7b-chat-hf"

# 加载模型
hf_model = AutoModelForCausalLM.from_pretrained(model_path)

# 计算参数数量
total_params = sum(p.numel() for p in hf_model.parameters())

# 计算模型大小(以字节为单位),假设每个参数为16位浮点数, 半精度浮点数(float16)
model_size_bytes = total_params * 2  # 2字节/参数

# 转换为更易于理解的单位:MB
model_size_mb = model_size_bytes / (1024 ** 2)

print(f"Total Parameters: {total_params}")
print(f"Model Size: {model_size_mb:.2f} MB")

总参数量为 6,738,415,616,模型大小以半精度浮点数(float16)计算,约为12.55GB,和实际模型文件大小基本一致。

三、Transformer模型参数量估算

下面我们来介绍一种Transformer模型通用的近似估算方法,假设词汇表大小为v,隐藏层维度为h

嵌入层: v \times h

多头自注意力层:含三个权重矩阵Q、K、V,加一个输出投影矩阵W,整个自注意力层的参数量为 4h^2,存在偏置参数加 4h

前馈神经网络层:两个线性变换,第一个从h扩展到4h,第二个再压缩回h,参数量为 8h^2,存在偏置参数加 5h

归一化层:分别用于对多头自注意力层和前馈神经网络层的输入进行归一化处理,同时包含缩放参数和平移参数,参数量为4h

RMSNorm与传统的层归一化不同,只有一个可训练的缩放参数,不包含偏置(平移)参数,它主要通过对输入的标准差进行归一化来稳定训练过程。

输出层: v \times h

Total\_Parameters = 2v\times h +l\times(12h^2+13h)

综上,l 层模型可训练模型参数量为 l(12h^2+13h)+2vh,当隐藏层维度 h 较大时,可忽略一次项,参数量可以近似为 p \approx 12\times l\times h^2

例如:估算GPT-3模型参数量,模型层数为96,隐藏层维度为12,288,代入公式可得 p \approx 12\times96 \times12288^2 \approx 174B,与模型给定参数基本一致。

### 计算深度学习模型参数数量 对于卷积神经网络(CNN),参数主要包括卷积核权重和其他需要学习的权值。具体来说: - 卷积层中的每个滤波器(即卷积核)都有自己的权重矩阵,以及偏置项。 - 对于全连接层而言,则由输入节点到输出节点之间的连接权重构成。 以AlexNet的第一个卷积层(CONV1)为例,假设该层有96个大小为\(11 \times 11\)且通道数为3的过滤器,并带有bias,则CONV1的总参数量可表示为\[ (11\times11\times3+1)\times96=34944\]这里额外加上了1是因为每一个filter还有一个对应的bias term[^1]。 ### 浮点运算次数(FLOPs) FLOPs代表完成一次正向传播过程中涉及的所有浮点操作的数量。它通常用来评估算法效率和硬件需求。针对特定类型的层,比如卷积层,可以按照如下方式估算: 给定一个尺寸为\(H_i\times W_i\)的特征图经过具有\(K_h\times K_w\)感受野、步幅stride s 和 padding p 的卷积处理后得到的新特征图为 \(H_o\times W_o\) ,那么单次卷积操作产生的FLOP数目大约等于\[2\times C_{in}\times C_{out} \times H_k \times W_k \times H_o \times W_o\] 其中因子2来源于每次乘法都会伴随至少一次加法;而当涉及到批量归一化(Batch Normalization)等其他组件时还需进一步增加相应的计算开销[^2]。 回到之前提到的例子,在不考虑激活函数等因素的情况下,如果输入图像分辨率为\(227\times227\)像素,通过上述公式可知CONV1层总的FLOPs约为\[34944\times55\times55=108249600\]。 需要注意的是实际应用中往往还会考虑到更多细节因素来精确统计整个网络架构下的总体FLOPs数值。 ```python def calculate_flops(input_size, kernel_size, output_channels, stride=1, padding=0): """ Calculate the number of floating point operations for a convolutional layer. Args: input_size (tuple): Input feature map size as tuple (height, width). kernel_size (int or tuple): Kernel dimensions either single value or pair (height,width). output_channels (int): Number of filters/output channels. stride (int or tuple, optional): Stride length(s). Defaults to 1. padding (int or str, optional): Padding applied before convolution (&#39;same&#39;, &#39;valid&#39; or int values). Default is no padding. Returns: float: Estimated FLOPs count per forward pass through this conv layer. """ if isinstance(kernel_size,int): kh,kw = kernel_size,kernel_size elif isinstance(kernel_size,(list,tuple)): kh,kw = kernel_size hi,wi=input_size ho=int((hi-kh+(2*padding))/stride)+1 wo=int((wi-kw+(2*padding))/stride)+1 flops_per_conv=(kh*kw)*output_channels*(ho*wo)*2 # Multiply by two because each multiply has an associated add operation return flops_per_conv print(f"FLOPs for Conv Layer with given specs:{calculate_flops((227,227),(11,11),96)}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值