现代大语言模型普遍采用Decoder-Only架构,以MiniMind为例,核心组件包括RMSNorm归一化、RoPE旋转位置编码、GQA分组查询注意力、SwiGLU门控前馈网络、残差连接及因果遮罩与KVCache。这些设计兼顾训练稳定、推理高效,参数量约64M,清晰展示了主流LLM的结构共性。
先来看一下现代大语言模型(LLM)的结构真相:它们几乎不约而同地选择了一种叫Decoder-Only的架构。和传统的Encoder-Decoder不同,Decoder-Only只干一件事——给定上文,预测下一个词。GPT、Llama、Qwen、DeepSeek……这些明星模型,核心结构都可以归结为这一条。

长期稳定更新的攒劲资源: >>>点此立即查看<<<
所以我们从MiniMind出发,把这个结构掰开揉碎了讲清楚。先看整体骨架:
MiniMindForCausalLM(因果语言模型外壳)
├── embed_tokens:Token Embedding,将token_id映射为768维向量
├── 8 × MiniMindBlock(Transformer层,逐层堆叠)
│ ├── RMSNorm → Attention(多头注意力) → 残差连接
│ └── RMSNorm → FeedForward(SwiGLU) → 残差连接
├── RMSNorm(最终归一化)
└── lm_head:线性投影层,将768维向量映射为6400维词表概率分布
这个结构看起来简洁,每个组件背后都有它的道理。我们一个个来看。
神经网络在逐层计算时,矩阵乘法会不断放大数值的尺度。如果不加约束,经过多层叠加后,数值要么溢出,要么梯度消失——训练根本没法稳定下来。归一化的作用就是在每个计算模块入口把数值拉回标准范围。
class RMSNorm(nn.Module):
def __init__(self, dim, eps=1e-5):
self.weight = nn.Parameter(torch.ones(dim)) # 可学习的缩放因子
def norm(self, x):
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
def forward(self, x):
return self.weight * self.norm(x.float())
RMSNorm和LayerNorm最大的区别是:它只对均方根做缩放,不减去均值。少了一步计算,速度更快,而在实践中效果相当。是典型的速度与性能平衡选择。
归一化改变的只是向量的“长度”(尺度),不改变向量的“方向”(维度间的比例关系)。语义信息主要编码在方向中,因此不会因为归一化而丢失。而且那个可学习的weight参数,就是模型用来恢复必要尺度信息的手段。
input_layernorm:Attention计算前的尺度校准post_attention_layernorm:FeedForward计算前的尺度校准final norm(MiniMindModel.norm):送入lm_head前的最终校准三者功能完全相同,只是位置不同。目的是确保每个计算模块都能接收到尺度正常的输入。
Transformer的自注意力机制本身是没有位置感知能力的。"我打你"和"你打我",如果不加位置信息,对模型来说其实是等价的。所以我们需要一种方式让模型知道每个词在序列中的位置。
RoPE的做法很巧妙:把每个位置的Q和K向量按特定角度旋转。位置不同,旋转的角度就不同。当两个词做点积时,结果只和它们的相对距离有关——这个特性是它的核心优势。
768维向量被拆成384对,每对分配一个不同的旋转频率:
freqs = 1.0 / (1000000 ** (torch.arange(0, dim, 2) / dim))
384个频率的组合,确保了任意两个位置的编码都是唯一的。这就好比钟表上秒针、分针、时针的组合,能唯一标识一个时刻。高频和低频的配合,让模型能在短距离和长距离间精确感知位置。
当推理时遇到的序列长度超过训练时的最大长度,高频维度的角度会进入模型从未见过的范围。YaRN的思路是:把这些高频维度的频率降低(除以一个factor),把远距离位置的角度“压缩”回模型熟悉的范围。听起来复杂,实际上是个很工程化的解决方案。
在标准多头注意力中,Q、K、V各有8个头,每个头独立计算注意力。推理时,我们需要缓存所有K、V头的历史状态(即KV Cache),显存开销非常大。当模型规模变大时,这个开销几乎是不可忽视的。
MiniMind采用了一个很实用的设定:Q头数=8,KV头数=4。也就是说,每2个Q头共享1组KV:
self.q_proj = nn.Linear(768, 8 * 96) # 8 个 Q 头
self.k_proj = nn.Linear(768, 4 * 96) # 4 个 KV 头
self.v_proj = nn.Linear(768, 4 * 96)
计算时通过repeat_kv把4组KV复制扩展为8组,与Q头数匹配。这样既保留了多头注意力的表达能力,又大幅减少了KV Cache的占用。
KV Cache减半,推理速度和显存都明显改善。Google在65B规模上验证过,这种设置带来的效果损失不到0.5%——可以忽略不计的代价换来了翻倍的效率提升,这笔账是算得过来的。
Attention处理的是词与词之间的关系,而FeedForward是对每个位置独立做知识加工。它的基本结构是:升维 → 激活 → 降维。
def forward(self, x):
gate = F.silu(self.gate_proj(x)) # 门控信号:每个维度的"开关"
info = self.up_proj(x) # 信息流:实际内容
return self.down_proj(gate * info) # 门控 × 信息 → 降维
相比于普通的ReLU激活函数,SwiGLU引入了一个门控机制。模型可以精细控制“哪些信息通过、通过多少”,而不是ReLU那种非开即关的二值方式。这让信息筛选变得更加灵活可控。
每个MiniMindBlock包含两次残差连接:
residual = hidden_states
hidden_states = self.self_attn(self.input_layernorm(hidden_states))
hidden_states += residual # 第一次残差
hidden_states = hidden_states + self.mlp(self.post_attention_layernorm(hidden_states)) # 第二次残差
每个子模块(Attention、FeedForward)都独立拥有一条残差通道。这保证了两个关键特性:
Decoder-Only架构的核心约束是:生成时只能看到“过去”,不能看到“未来”。实现上,通过一个上三角矩阵把未来位置的注意力分数设为负无穷:
scores[:, :, :, -seq_len:] += torch.full((seq_len, seq_len), float("-inf")).triu(1)
推理时,已经生成好的token的K、V是固定的、不会变化的。把它们缓存起来后,每一步只需要对新token做计算即可,避免了大量重复运算。这是实际工程中大幅提升生成速度的关键设计。
MiniMind-3的参数配置如下:
| 参数 | 值 | 说明 |
|---|---|---|
| hidden_size | 768 | 向量维度 |
| num_hidden_layers | 8 | Transformer 层数 |
| num_attention_heads | 8 | Q 头数 |
| num_key_value_heads | 4 | KV 头数(GQA) |
| vocab_size | 6400 | 词表大小 |
| intermediate_size | 2432 | FFN 中间维度 |
| max_position_embeddings | 32768 | 最大位置数 |
总参数量大约64M——只有GPT-3的1/2700。用小模型讲清楚大模型的核心设计,这是MiniMind的设计初衷。
回头来看,当前主流LLM的结构已经高度趋同:Decoder-Only + RoPE + GQA + SwiGLU + RMSNorm + Pre-Norm。这些组件的选择不是随机的,每一个都经过了充分的理论验证和实践检验。
说到底,模型的本质就是一组参数矩阵。训练的过程,无非是把这些矩阵从随机值调整成能够准确预测下一个token的有意义数值。结构设计的核心,就是选择合适的计算组件、用合理的方式连接它们,让梯度传播顺畅、训练稳定、推理高效。这才是工程之美所在。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述