前面已经看过 tokenizer、dataset 和训练循环。现在进入模型结构。
MiniMind 的模型代码集中在:
model/model_minimind.py
第一次看这个文件时,很容易被各种类名绕住。其实可以先抓三层:
MiniMindConfig -> MiniMindForCausalLM -> MiniMindModel
这三层分别对应:
模型配置 -> 语言模型外壳 -> Transformer 主干
1. MiniMindConfig:模型的图纸
MiniMindConfig 保存模型结构参数。
一些关键默认值包括:
| 参数 | 默认值 | 含义 |
|---|---|---|
vocab_size | 6400 | 词表大小。 |
hidden_size | 768 | hidden state 维度。 |
num_hidden_layers | 8 | Transformer block 层数。 |
num_attention_heads | 8 | Q head 数量。 |
num_key_value_heads | 4 | K/V head 数量。 |
intermediate_size | 约 2432 | FFN 中间层维度。 |
dropout | 0.0 | dropout 概率。 |
rms_norm_eps | 1e-6 | RMSNorm 的稳定项。 |
rope_theta | 1e6 | RoPE 频率参数。 |
use_moe | False | 是否启用 MoE。 |
这些参数会决定模型里各个矩阵的形状。
例如:
hidden_size = 768 num_attention_heads = 8 head_dim = 768 / 8 = 96
所以你调试 Attention 时看到的 head 维度 96,就是从这里来的。
2. vocab_size 会影响哪些参数
vocab_size 不只是 tokenizer 的概念,它会直接影响模型参数量。
MiniMind 里有两个和词表相关的层:
self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
如果 vocab_size=6400,hidden_size=768:
embedding 参数量 = 6400 * 768 lm_head 参数量 = 768 * 6400
如果没有权重共享,这两块都很大。
很多模型会把 embedding 和 lm_head 绑定:
self.lm_head.weight = self.model.embed_tokens.weight
这样可以减少参数量,也让输入 token embedding 和输出 token 分类共享同一套语义空间。
3. MiniMindForCausalLM:语言模型外壳
MiniMindForCausalLM 是面向 causal language modeling 的最外层模型。
它主要负责:
创建 MiniMindModel 主干; 创建 lm_head; 处理 labels 和 loss; 组织返回值; 实现 generate。
可以把它理解成:
Transformer backbone + 词表预测头 + 训练/推理接口
它的 forward 大致是:
input_ids -> MiniMindModel -> hidden_states -> lm_head -> logits -> 如果传入 labels,则计算 loss
logits 的形状是:
[batch_size, seq_len, vocab_size]
每个位置都预测下一个 token 的词表分数。
4. 为什么叫 CausalLM
CausalLM 里的 causal 指的是因果语言模型。
它的限制是:
当前位置只能看当前位置及之前的 token,不能看未来 token。
这和生成任务完全一致:
生成第 t 个 token 时,只能依赖前面已经生成的 token。
所以 CausalLM 通常使用:
decoder-only Transformer masked self-attention next-token prediction
MiniMind 就是这种结构。
5. MiniMindModel:Transformer 主干
MiniMindModel 是真正堆叠 Transformer block 的部分。
它主要包含:
self.embed_tokens self.dropout self.layers self.norm
可以画成:
input_ids | v Embedding | v Dropout | v MiniMindBlock x N | v Final RMSNorm | v hidden_states
这里的 MiniMindBlock x N 中,N 就是:
config.num_hidden_layers
默认是 8 层。
6. embedding 是 lookup,不是普通矩阵乘法
embed_tokens 的定义是:
nn.Embedding(vocab_size, hidden_size)
它可以理解成一个表:
[vocab_size, hidden_size]
每个 token id 对应表里的一行。
当输入是:
[12, 35, 98]
embedding 做的是查表:
取第 12 行 取第 35 行 取第 98 行
输出形状变成:
[seq_len, hidden_size]
所以它更像 lookup table,而不是对 one-hot 做显式矩阵乘法。数学上两者等价,工程上查表更高效。
7. Dropout 为什么是 0
MiniMind 默认:
dropout = 0.0
这意味着训练和推理时都不会随机丢弃 hidden state。
在现代 LLM 中,dropout 不一定总是开启。尤其是:
数据量较大; 模型规模较大; 训练策略较稳定; 更追求训练/推理一致性;
时,dropout 可能设得很低,甚至为 0。
但 dropout 本身仍然是经典正则化手段。它的作用是:
训练时随机屏蔽部分激活,降低过拟合。
小模型、小数据或特定微调任务中,dropout 仍然可能有价值。
8. KV Cache 和 start_pos
MiniMindModel.forward 里还会处理:
past_key_values start_pos use_cache
这些主要服务于推理。
训练时通常一次性处理完整序列:
seq_len = N
推理 decode 时,每次只新增一个 token。如果每次都从头计算所有历史 token,成本会很高。
KV Cache 的思路是:
历史 token 的 K/V 已经算过了,缓存起来; 下一步只计算新 token 的 Q/K/V; 再和历史 K/V 一起做 attention。
start_pos 则告诉 RoPE 等位置相关逻辑:当前新 token 在完整序列中的位置从哪里开始。
所以它和 KV Cache 是配套的。
9. 三层关系总结
可以把三者关系记成:
MiniMindConfig 决定模型有多大、多少层、多少 head。 MiniMindForCausalLM 包装主干模型,负责 logits、loss 和 generate。 MiniMindModel 真正的 Transformer backbone,负责 embedding、blocks、norm。
如果从调用链看:
MiniMindForCausalLM.forward
-> MiniMindModel.forward
-> embed_tokens
-> MiniMindBlock x N
-> norm
-> lm_head
-> loss / logits
10. 小结
学习模型结构时,不要一开始就陷入每一行代码。
先建立这张地图:
配置层:MiniMindConfig 模型外壳:MiniMindForCausalLM 主干网络:MiniMindModel 基本单元:MiniMindBlock
这篇文章讲前三层。下一篇继续进入 MiniMindBlock,也就是每一层 Transformer decoder block 内部到底做了什么。