手算 LLM 参数量:以 Qwen2.5-32B 为例逐层拆解

以 Qwen2.5-32B-Instruct 的 config 为例,按嵌入层、多头注意力、前馈网络与归一化层逐项推导 Transformer 模型的参数量计算公式,并给出各层参数占比的完整推导过程。

已知 Qwen2.5-32B-Instruct 模型的架构信息,基于其网络结构来模型计算的参数量。

Qwen2.5-32B-Instruct模型网络结构

模型的config文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"architectures": [
"Qwen2ForCausalLM"
],
"attention_dropout": 0.0,
"bos_token_id": 151643,
"eos_token_id": 151645,
"hidden_act": "silu",
"hidden_size": 5120,
"initializer_range": 0.02,
"intermediate_size": 27648,
"max_position_embeddings": 32768,
"max_window_layers": 70,
"model_type": "qwen2",
"num_attention_heads": 40,
"num_hidden_layers": 64,
"num_key_value_heads": 8,
"rms_norm_eps": 1e-06,
"rope_theta": 1000000.0,
"sliding_window": 131072,
"tie_word_embeddings": false,
"torch_dtype": "bfloat16",
"transformers_version": "4.43.1",
"use_cache": true,
"use_sliding_window": false,
"vocab_size": 152064
}

以下是详细的计算推导过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def calculate_total_parameters(config):
# 第一步:计算嵌入层参数量
embedding_params = config['vocab_size'] * config['hidden_size']

# 第二步:计算每层的参数量
ffn_params = 3 * (config['hidden_size'] * config['intermediate_size']) # 三个线性层
attention_params = 2 * config['hidden_size'] * config['hidden_size']*config['num_key_value_heads']/config['num_attention_heads'] + config['hidden_size'] * config['hidden_size'] # 多头注意力机制部分 Q, K, V
output_projection_params = config['hidden_size'] * config['hidden_size'] # 输出投影

# 第三步:计算每层的总参数量
layer_params = ffn_params + attention_params + output_projection_params

# 第四步:计算lm_head层参数量
lm_head_params = config['vocab_size'] * config['hidden_size']

# 第五步:总参数量
total_params = embedding_params + layer_params * config['num_hidden_layers'] + lm_head_params
return total_params/ 1e9


# 总参数量
total_params = calculate_total_parameters(config)
print(f"总参数量: {total_params:.2f} B")

一、模型层(model)

1. 嵌入层 (embedding)

将离散的文本 token 转换为连续的向量表示,同时结合位置编码(如 RoPE)赋予序列位置信息,以便模型理解 token 在序列中的位置关系。

嵌入层参数量为:config[‘vocab_size’] * config[‘hidden_size’] = 152064×5120=778,567,680

  • 输入特征维度:152064
  • 输出特征维度:5120

    2. 单个 Transformer 层

每个 Transformer 层的 MLP 包括以下部分:

前馈网络(FFN/MLP)

a. 门控投影

门控投影参数量为:config[‘hidden_size’] * config[‘intermediate_size’] = 5120×27648=141,557,760

  • 输入特征维度:5120
  • 输出特征维度:27648
  • 偏置项参数量:27648

b. 上采样投影

上采样参数量为:config[‘hidden_size’] * config[‘intermediate_size’] = 5120×27648=141,557,760

  • 输入特征维度:5120
  • 输出特征维度:27648
  • 偏置项参数量:27648

c. 下采样投影

下采样参数量为:config[‘hidden_size’] * config[‘intermediate_size’] = 5120×27648=141,557,760

  • 输入特征维度:27648
  • 输出特征维度:5120
  • 偏置项参数量:5120

    多头注意力机制(Self-Attention

通过 Q(查询)、K(键)、V(值)的交互,捕捉序列中不同位置 token 之间的关系,使模型能够理解上下文中的依赖关系:

a. Q (查询向量) 投影

Query向量参数量为:config[‘hidden_size’] * config[‘hidden_size’] = 5120×5120=26,214,400

  • 输入特征维度:5120
  • 输出特征维度:5120
  • 偏置项参数量:5120

b. K (键向量) 投影(GQA——5120*8/40=1024)

Key向量参数量为:config[‘hidden_size’] * config[‘hidden_size’]*config[‘num_key_value_heads’]/config[‘num_attention_heads’] =5120×1024=5,242,880

  • 输入特征维度:5120
  • 输出特征维度:1024
  • 偏置项参数量:1024

c. V (值向量) 投影(GQA——5120*8/40=1024)

Value向量参数量为:config[‘hidden_size’] * config[‘hidden_size’]*config[‘num_key_value_heads’]/config[‘num_attention_heads’] =5120×1024=5,242,880

  • 输入特征维度:5120
  • 输出特征维度:1024
  • 偏置项参数量:1024

d. O (输出) 转换

Qutout向量参数量为:config[‘hidden_size’] * config[‘hidden_size’] = 5120×5120=26,214,400

  • 输入特征维度:1024

  • 输出特征维度:5120

  • 偏置项参数量:5120

    *输入层归一化 (input layernorm) *

  • 输入特征维度:5120

  • 归一化层参数量:5120

    *后注意力归一化 (post attention layernorm) *

  • 输入特征维度:5120

  • 归一化层参数量:5120

    3. “norm” 归一化层

稳定训练过程,加速收敛,确保各层输入的分布一致,提升模型性能。

  • 输入特征维度:5120
  • 归一化层参数量:5120

    4. 位置旋转嵌入 (rotary_emb)

位置旋转嵌入的参数量需要根据具体实现来确定,通常与模型的序列长度和特征维度相关。由于具体实现细节未提供,假设其参数量可以忽略不计或已包含在其他层的参数计算中。

二、线性层 (lm_head)

将 Transformer 层输出的特征向量映射到具体任务的输出空间,如文本生成中的词表,用于预测下一个 token。

线性层参数量为:config[‘hidden_size’] * config[‘vocab_size’] = 5120×152064=778,567,680

  • 输入特征维度:5120
  • 输出特征维度:152064

    三、模型总参数量

total_params = embedding_params + layer_params * config[‘num_hidden_layers’] + lm_head_params=32.76275712B

通常模型的参数量会四舍五入到合适的精度,因此可以表示为约 32 billion 参数,这与模型名称中的 “32B” 一致。

四、模型推理计算量

1、预填充Prefilling_FLOPs

假设输入长度为1024,输出为1024,batch_size=1,模型的config文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
config = {
"attention_dropout": 0.0,
"bos_token_id": 151643,
"eos_token_id": 151643,
"hidden_act": "silu",
"hidden_size": 5120,
"initializer_range": 0.02,
"intermediate_size": 27648,
"max_position_embeddings": 131072,
"max_window_layers": 64,
"model_type": "qwen2",
"num_attention_heads": 40,
"num_hidden_layers": 64,
"num_key_value_heads": 8,
"rms_norm_eps": 1e-05,
"rope_theta": 1000000.0,
"sliding_window": 131072,
"torch_dtype": "bfloat16",
"transformers_version": "4.43.1",
"vocab_size": 152064,
"prompt_token_length": 1024,
"output_token_length": 1024,
"batch_size": 1,
}

以下是详细的推导计算过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def calculate_prefilling_FLOPs(config):
# 第一步:Q、K、V投影计算量
query_projection_flops = 2*config['prompt_token_length'] * config['hidden_size']**2
key_projection_flops = 2* config['prompt_token_length'] * config['hidden_size']**2 * config['num_key_value_heads']/config['num_attention_heads']
value_projection_flops = 2* config['prompt_token_length'] * config['hidden_size']**2 * config['num_key_value_heads']/config['num_attention_heads']

# 第二步:self-attention计算量
# kv 在GQA的状态下,kv的存储量变小,但是计算量不变,因为K和V会有广播
Q_K_flops = 2* config['prompt_token_length']**2 * config['hidden_size']
A_V_flops = 2* config['prompt_token_length']**2 * config['hidden_size']
# 输出投影计算量
output_projection_flops = 2*config['prompt_token_length'] * config['hidden_size']**2

# 第三步:前馈网络计算量
## swiGLu 有三次线性变换
ffn_flops = 3* 2* config['prompt_token_length'] * config['hidden_size'] * config['intermediate_size']
layer_flops = query_projection_flops + key_projection_flops + value_projection_flops + Q_K_flops + A_V_flops + output_projection_flops + ffn_flops

# 第四步:计算量总计
total_flops = layer_flops * config['num_hidden_layers']*config['batch_size']
return total_flops/ 1e12

total_prefilling_flops = calculate_prefilling_FLOPs(config)
print(f"Prefilling阶段总计算量: {total_prefilling_flops:.2f} TFLOPs")

Prefilling阶段总计算量: 65.28 TFLOPs

2、解码Decoding_FLOPs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def calculate_decoding_FLOPs_per_token(config):
# 第一步:Q、K、V投影计算量
query_projection_flops = 2* config['hidden_size']**2
# K,v投影计算量,每次计算一个token的kv
key_projection_flops = 2* config['hidden_size']**2 * config['num_key_value_heads']/config['num_attention_heads']
value_projection_flops = 2* config['hidden_size']**2 * config['num_key_value_heads']/config['num_attention_heads']

# 第二步:self-attention计算量
# kv cache的状态下,KV的大小的随着step的增加而增加,从初始的prompt_token_length 到最终的prompt_token_length+output_token_length
Q_K_flops = 2* (config['prompt_token_length']+(1+config['output_token_length'])/2) * config['hidden_size']
A_V_flops = 2* (config['prompt_token_length']+(1+config['output_token_length'])/2) * config['hidden_size']
# 输出投影计算量
output_projection_flops = 2* config['hidden_size']**2

# 第三步:前馈网络计算量
# swiGLu 有三次线性变换
ffn_flops = 3* 2* config['hidden_size'] * config['intermediate_size']
layer_flops = query_projection_flops + key_projection_flops + value_projection_flops + Q_K_flops + A_V_flops + output_projection_flops + ffn_flops

# 第四步:计算量总计
total_flops = layer_flops * config['num_hidden_layers']*config['batch_size']
return total_flops/ 1e12

decoding_FLOPs_per_token = calculate_decoding_FLOPs_per_token(config)
print(f"平均每个token的计算量: {decoding_FLOPs_per_token:.2f} TFLOPs")

平均每个token的计算量: 0.06 TFLOPs

3. 理论性能和GPU利用率

Prefilling

TTFT = Prefilling_FLOPs / GPU_FLOPS

计算得Prefilling阶段总计算量: 65.28 TFLOPs

理论TTFT = 65.28TFLOPs/312TFLOPS = 209ms

但是GPU的使用率一般没办法跑满60%左右,所以延迟会更高些。

Decoding

计算得每一个batch生成的计算量为0.06TFLOPs

理论上的throughput = 312TFLOPS/0.06TFLOPs = 5200token/s

但是由于GPU的利用率,可以估计出来的TPS会更低

快速估计方法

Prefilling_FLOPs = 2 * Batch_size * Prompt_size * Parameters= 2 * 1 * 1024 * 32B = 64TFLOPs

  • 估计TTFT = 64TFLOPs/312TFLOPs=205ms
    Decoding_FLOPs_Per_Step = 2 * Batch_size * Parameters = 2 * 1 * 32B = 0.064TFLOPs

  • 快速估计的理论Throughput = 148TFLOPS/0.064TFLOPs = 4875token/s

本文结束 感谢您的阅读