Qwen2模型架构

Qwen2.5是阿里云最新推出的开源大型语言模型系列,相比Qwen2,新版本在多个方面实现了显著提升,包括知识掌握、编程能力、数学能力以及指令执行等。


1. Qwen2 整体结构(左侧部分)

1. 文本输入 → Tokenizer → input_ids

  • 输入是一串文本

  • Tokenizer 转成整数:

    input_ids: (batch, seq_len)


2. input_ids → Embedding 层

将 token id 查表得到向量:

复制代码
hidden_states: (batch, seq_len, hidden_size)

这是 Transformer 第 0 层的初始特征。


3. hidden_states 传入多个 Transformer Layers

每层结构:

复制代码
RMSNorm
→ Self-Attention
→ 残差连接
→ RMSNorm
→ FFN(MLP)
→ 残差连接

每层都更新 hidden_states:

复制代码
hidden_states = Layer_i(hidden_states)

4. Transformer 最后一层输出 → Final RMSNorm

Qwen2(LLaMA 系)在所有层结束后会再加一次最终 RMSNorm:

复制代码
hidden_states = FinalRMSNorm(hidden_states)

作用:

让最后阶段的数值更加稳定(作为 logits 的输入)。


5. Final hidden_states → 线性投影(LM Head)

最后一层的 hidden_states 通过一个线性层(权重与 embedding 通常共享):

复制代码
logits = hidden_states @ W_vocab^T

形状:

复制代码
logits: (batch, seq_len, vocab_size)

例如 vocab_size = 151,000(Qwen2 实际大小)。


6. logits → Softmax → 预测下一个 token

取所有batch的最后一个token在 vocab 上的所有概率 logits。:

复制代码
probs  = softmax(logits[:, -1, :])
next_id = argmax(probs)

📌 (伪代码版)

复制代码
input_ids
   ↓
token_embedding
   ↓
hidden_states (layer0)

for layer in transformer_layers:
    hidden_states = RMSNorm(hidden_states)
    hidden_states = hidden_states + Attention(hidden_states)
    hidden_states = hidden_states + FFN(RMSNorm(hidden_states))

# 最后一层输出:
decoder_hidden = hidden_states
logits = decoder_hidden @ token_embedding.weight.T  # shape: (batch, seq_len, vocab_size)

# 取最后一个 token logits
last_token_logits = logits[:, -1, :]  # shape: (batch, vocab_size)

# Softmax → next token
probs = softmax(last_token_logits, axis=-1)
next_id = argmax(probs, axis=-1)  # shape: (batch,)

2. Decoder Layer 结构

  • Hidden_states 经过 RMSNorm 正则化后,传递给 Attention 模块。
  • 在 Attention 模块内,输入分为 Query、Key 和 Value,用于注意力机制的计算。
  • 经过 Attention 后的输出再经过另一个 RMSNorm 处理,并通过 MLP(多层感知器)进一步处理。
  • 每一步都有残差连接(Residual),保证信息流动不会丢失。

📌 (伪代码版)

powershell 复制代码
hidden = hidden
hidden = hidden + Attention(RMSNorm(hidden))
hidden = hidden + MLP(RMSNorm(hidden))

📌 换成一句话解释:

Decoder Layer 做两件事:

  • Attention:让 token "互相看见"
  • MLP:非线性建模能力
    并且每一步前面都有 RMSNorm、每一步后面都有残差。

3.Attention机制

  • Hidden_states 分别生成 Query、Key 和 Value,Q和K应用 Rotary positional embedding
    进行位置编码。
  • 这些编码后的 Query、Key 和 Value 经过注意力计算,生成 Attention_weight。
  • Attention_weight 通过 Softmax 归一化,并与 Value 相乘(Matmul),最后输出为 O_Linear。

    Qwen2 使用 GQA 通过减少 K/V 头数来节省显存并加速推理,同时保持 Query 头数不变,用更少的代价获得接近 MHA 的性能。
方案 Query heads KV heads 显存/速度 性能
MHA 32 32 显存最大、最慢 最强
MQA 32 1 显存最小、最快 有性能损失
GQA 32 8 节省 4 倍 KV 缓存 接近 MHA 性能

4.MLP

Qwen 的 MLP 是一种 门控前馈网络(Gated MLP / SwiGLU MLP),结构如下:

复制代码
(hidden_size → intermediate_size → hidden_size)

包含三层 Linear 和一个激活函数:

  • gate_proj:Linear(hidden → intermediate)
  • up_proj:Linear(hidden → intermediate)
  • act_fn:SiLU(或其他 activation)
  • down_proj:Linear(intermediate → hidden)

Forward 计算流程

MLP 的 forward 逻辑是:

python 复制代码
hidden = act(gate_proj(x)) * up_proj(x)
output = down_proj(hidden)

也就是:

  1. gate_proj(x) 送入激活函数(如 SiLU)
  2. up_proj(x)逐元素相乘(门控)
  3. 再送入 down_proj 回到原维度

核心思想

  • 不是普通 MLP

    不是常规的 Linear → Act → Linear,而是使用 GLU 结构(Gated Linear Unit)。

  • 两个上投影:gate_proj & up_proj

    其中 gate_proj 控制信息的"通过量",相当于一个门(gate)。

  • 更强的表达能力,但参数几乎不变

    Gated MLP(SwiGLU)在 LLM 中效果显著更好,因此被 LLaMA、Qwen、GPT-NeoX 等广泛使用。


5.输出层

Qwen2 输出层 = RMSNorm(稳定 hidden_states)→ Linear(映射到词表)→ Softmax(得到概率),最终生成 logits,用于预测下一个 token。

模块 作用 输入形状 输出形状
RMSNorm 对 hidden_states 做尺度归一化 (batch, seq_len, hidden_size) (batch, seq_len, hidden_size)
Linear (lm_head) 映射到词表维度 (batch, seq_len, hidden_size) (batch, seq_len, vocab_size)
Softmax 生成概率分布 (batch, seq_len, vocab_size) (batch, seq_len, vocab_size)

2.Qwen2-7B的配置参数

powershell 复制代码
 {
  "architectures": [
    "Qwen2ForCausalLM"
  ],
  "attention_dropout": 0.0,
  "bos_token_id": 151643,
  "eos_token_id": 151645,
  "hidden_act": "silu",
  "hidden_size": 3584,
  "initializer_range": 0.02,
  "intermediate_size": 18944,
  "max_position_embeddings": 32768,
  "max_window_layers": 28,
  "model_type": "qwen2",
  "num_attention_heads": 28,
  "num_hidden_layers": 28,
  "num_key_value_heads": 4,
  "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
}
参数 说明
architectures ["Qwen2ForCausalLM"] 模型类,Qwen2 的自回归语言模型
model_type "qwen2" 模型类型标识,用于 Transformers 框架
bos_token_id 151643 句首 token ID(Begin of Sentence)
eos_token_id 151645 句尾 token ID(End of Sentence)
vocab_size 152064 模型词表大小,也决定输出 logits 维度
tie_word_embeddings false 输入 embedding 和输出 lm_head 是否共享权重
hidden_size 3584 Transformer 隐层维度 d_model
intermediate_size 18944 MLP 中间层维度(FeedForward hidden size)
hidden_act "silu" MLP 激活函数(SiLU / SwiGLU 类型)
num_hidden_layers 28 Transformer 层数
num_attention_heads 28 Query 头数
num_key_value_heads 4 Key/Value 头数(GQA,Query 多头共享少量 KV)
attention_dropout 0.0 注意力权重 Dropout 概率
rms_norm_eps 1e-06 RMSNorm 的 epsilon 防止除零
rope_theta 1000000.0 RoPE(旋转位置编码)频率缩放参数
max_position_embeddings 32768 模型最大支持 token 长度
max_window_layers 28 滑动窗口/长序列允许参与的最大层数
sliding_window 131072 滑动窗口总长度(长序列生成时使用)
use_sliding_window false 是否开启滑动窗口上下文
initializer_range 0.02 权重初始化范围
torch_dtype "bfloat16" PyTorch 数据类型,用 bfloat16 节省显存
use_cache true 是否在推理时缓存 KV 提升自回归生成速度
transformers_version "4.43.1" Transformers 框架版本,用于兼容性

3.Qwen源码

1.RMS归一化类

1️⃣ 稳定训练

  • RMSNorm 可以 保持每个样本的特征向量在一个稳定的尺度上,避免梯度在深层网络中爆炸或消失。
  • 与传统的 LayerNorm 不同,它 不减均值,只归一化均方根,计算更简单,训练更稳定。

2️⃣ 加速收敛

  • 归一化后,网络的输入特征分布更均匀,使得优化器更容易更新参数。
  • 在 Transformer 类模型中,RMSNorm 可以让模型更快收敛,尤其是在大模型(如 Qwen2)中。

powershell 复制代码
class Qwen2RMSNorm(nn.Module):
    def __init__(self, hidden_size, eps=1e-6):
        """
        Qwen2RMSNorm is equivalent to T5LayerNorm
        """
        super().__init__()
        self.weight = nn.Parameter(torch.ones(hidden_size))
        self.variance_epsilon = eps

    def forward(self, hidden_states):
        input_dtype = hidden_states.dtype
        hidden_states = hidden_states.to(torch.float32)
        variance = hidden_states.pow(2).mean(-1, keepdim=True)
        hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
        return self.weight * hidden_states.to(input_dtype)

2.Attention

python 复制代码
def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
    """
    This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
    num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
    """
    batch, num_key_value_heads, slen, head_dim = hidden_states.shape
    if n_rep == 1:
        return hidden_states
    hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
    return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)

1️⃣ 功能

  • 作用 :将多组 key/value 张量(num_key_value_heads)扩展到多头注意力所需的总头数(num_attention_heads)。

  • 输入

    • hidden_states:形状 (batch, num_key_value_heads, seq_len, head_dim)
    • n_rep:每个 key/value head 需要重复的次数
  • 输出

    • 形状 (batch, num_key_value_heads * n_rep, seq_len, head_dim)
    • 逻辑上把每个 key/value head 重复 n_rep

2️⃣ 核心逻辑

python 复制代码
hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
  • 新增维度 None:在 key/value head 后增加一个维度,用于重复
  • expand :逻辑上重复 n_rep不占用额外显存,共享原始数据
python 复制代码
.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
  • reshape :把 (num_key_value_heads, n_rep) 展平为总头数 num_key_value_heads * n_rep,方便矩阵乘法计算

3️⃣ 特点

  1. 高效 :使用 expand + reshape,不会立刻增加显存
  2. 逻辑复制:每个 key/value head 对应多个 query head(分组注意力)
  3. 通用性 :适用于多头注意力中 key/value head 少于 query head 的情况

4️⃣ 举例

python 复制代码
import torch
x = torch.arange(2*2*3*4).reshape(2, 2, 3, 4)  # batch=2, kv_heads=2, seq_len=3, head_dim=4
y = repeat_kv(x, 3)  # 每个 head 复制3次
print(y.shape)  # (2, 6, 3, 4)
  • 每个 key/value head 被逻辑上重复 3 次,总头数变为 6
  • 底层内存仍然是原来的 2 个 key/value head,没有占用 3 倍显存

总结一句话

repeat_kvexpand + reshape 将少量 key/value head 逻辑上复制到多头注意力所需的总头数,高效共享内存,便于分组注意力计算。


python 复制代码
def eager_attention_forward(
    module: nn.Module,
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
    attention_mask: Optional[torch.Tensor],
    scaling: float,
    dropout: float = 0.0,
    **kwargs,
):
    key_states = repeat_kv(key, module.num_key_value_groups)
    value_states = repeat_kv(value, module.num_key_value_groups)

    attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
    if attention_mask is not None:
        causal_mask = attention_mask[:, :, :, : key_states.shape[-2]]
        attn_weights = attn_weights + causal_mask

    attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
    attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training)
    attn_output = torch.matmul(attn_weights, value_states)
    attn_output = attn_output.transpose(1, 2).contiguous()

    return attn_output, attn_weights

经典的GQA,无需多说。

3.MLP

powershell 复制代码
import torch
import torch.nn as nn

# 定义激活函数映射
ACT2FN = {
    "relu": nn.ReLU(),
    "gelu": nn.GELU(),
    "silu": nn.SiLU(),  # Swish 激活
}

# 配置类
class Config:
    def __init__(self, hidden_size=8, intermediate_size=16, hidden_act="silu"):
        self.hidden_size = hidden_size
        self.intermediate_size = intermediate_size
        self.hidden_act = hidden_act

# Qwen2MLP 模块
class Qwen2MLP(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.config = config
        self.hidden_size = config.hidden_size
        self.intermediate_size = config.intermediate_size
        self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
        self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
        self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
        self.act_fn = ACT2FN[config.hidden_act]

    def forward(self, x):
        # GLU 前向传播
        return self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))

# 测试函数
def test_qwen2mlp():
    # 使用 config 创建模型
    config = Config(hidden_size=8, intermediate_size=16, hidden_act="silu")
    mlp = Qwen2MLP(config)
    
    # 随机输入: batch_size=2, seq_len=4, hidden_size=8
    x = torch.randn(2, 4, config.hidden_size)
    
    # 前向传播
    output = mlp(x)
    
    print("输入形状:", x.shape)
    print("输出形状:", output.shape)
    print("输出示例:", output)

# 运行测试
if __name__ == "__main__":
    test_qwen2mlp()
相关推荐
AngelPP3 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年4 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼4 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS4 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区5 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈5 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang6 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx
shengjk17 小时前
NanoClaw 深度剖析:一个"AI 原生"架构的个人助手是如何运转的?
人工智能
西门老铁9 小时前
🦞OpenClaw 让 MacMini 脱销了,而我拿出了6年陈的安卓机
人工智能