minimind/learning/day01/01_prereading.md

89 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Day 1 Prereading: 环境、项目地图、Decoder-only 基础
## 今日目标
你今天要建立项目全局地图,知道 MiniMind 的模型结构、训练脚本、数据集、评测入口分别在哪里。不要急着长时间训练,先确认环境、GPU、依赖和最小 forward 能跑通。
## 必须掌握的基础概念
- Decoder-only Transformer:输入 token 序列,预测下一个 token。
- Causal LM loss:第 `t` 个位置的 logits 预测第 `t+1` 个 token。
> **笔记:** Causal = 只能看前面的 token(上三角 mask)。Loss = `CrossEntropy(logits[:-1], labels[1:])`,即位置 t 预测 t+1。与 BERT (Masked LM, 双向) 相对。
- Tokenizer:把文本变成 token id,模型只看 token id。
> **笔记:** Tokenizer 由两个文件构成:`tokenizer.json`(词表+切词规则)和 `tokenizer_config.json`(特殊 token 定义 + chat_template 对话格式模板)。关键特殊 token:id0=PAD, id1=BOS(`<|im_start|>`), id2=EOS(`<|im_end|>`), id21-22=tool_call, id25-26=think。
> 切词方式:BPE + ByteLevel。先把文本转 UTF-8 bytes(任何语言都不会 UNK),再按 6108 条训练好的合并规则把高频 byte 组合合并成 token。词表 6400,中文约 1.5-1.7 字符/token。
- Embedding / LM Head:输入词表映射到 hidden,输出 hidden 映射回 vocab logits。
> **笔记:** 接力关系:Tokenizer (文本→id, 模型外部) → Embedding (id→向量, 入口) → LM Head (向量→logits, 出口)。MiniMind 中 Embedding 和 LM Head 共享权重 (`tie_word_embeddings=True`)。
> Hidden = Embedding 只是起点(查表),经过 8 层 Transformer 融合上下文后的向量才是真正的 hidden (768维)。Vocab logits = hidden @ LM_Head矩阵(768×6400) 得到的得分向量 (6400维),softmax 后变概率。
> **Weight Tying 原理:** Embedding `[6400,768]` 和 LM Head `[768,6400]` 互为逆操作(id→向量 vs 向量→得分),共享同一份权重(LM Head = Embedding 转置)。省 490 万参数(占 MiniMind 约 20%),且让编码/解码共用语义空间,效果更好。GPT-2/LLaMA/Qwen 均采用。
- Pre-Norm:attention/MLP 前先做 RMSNorm,训练更稳定。
- Flash Attention:优化注意力计算的算法,通过分块(tiling)在 GPU SRAM 内完成计算,避免将 O(n²) 注意力矩阵反复搬运到慢速显存(HBM),数学等价但速度快 2-4x、显存降至 O(n)。PyTorch 2.0+ 通过 `F.scaled_dot_product_attention` 内置支持。
- RoPE:旋转位置编码,把位置信息注入 Q/K。
> **笔记:** RoPE 不是专为 Causal LM 发明,而是解决通用问题:Transformer 本身无位置感知。演进:Learned Absolute (固定长度) → Sinusoidal → RoPE (旋转 Q/K,天然编码相对距离,可外推)。Causal LM 对长序列需求大,所以 RoPE 在此场景尤其流行。
> **YaRN 外推:** `max_position_embeddings`(32K) > 训练长度 `max_seq_len`(8K),靠 YaRN 实现。原理:把 RoPE 频率分三区——低频(长距离)压缩适配、高频(短距离)保持不变、中频渐进过渡。避免简单等比缩放丢失近距离精度。对应参数:`yarn_scale`、`yarn_beta_fast/slow`。
- GQA:query heads 多于 key/value heads,降低 KV cache 和计算成本。
> **笔记:** MHA 8Q:8KV → GQA 8Q:4KV → MQA 8Q:1KV。MiniMind 用 GQA (每2个Q头共享1对KV),KV cache 省50%。
- SwiGLU:现代 LLM 常用 FFN 结构。
> **笔记:** `Down(SiLU(Gate(x)) * Up(x))`。比标准 FFN 多一个 Gate 矩阵 (参数+50%),Gate 起可学习门控作用,选择性保留信息。LLaMA/Qwen/DeepSeek 均采用。
## 面试考点
- 为什么 causal LM 训练时 logits 和 labels 要 shift?
> **答:** 自回归目标是"用位置 t 预测 t+1"。logits 的最后一个位置没有下一个 token 可预测,labels 的第一个 token 没有上文来预测它,所以 `logits[:-1]` 对齐 `labels[1:]`。不 shift 的话预测目标就错位了。
- Decoder-only 和 Encoder-Decoder 在训练目标上有什么区别?
> **答:** Decoder-only(GPT):所有 token 统一做从左到右的 next-token prediction,输入和输出共享同一个序列。Encoder-Decoder(T5):Encoder 双向编码输入(无因果 mask),Decoder 自回归生成输出,两者是分开的序列。Decoder-only 更简单,规模扩展更直接,是当前主流。
- RMSNorm 和 LayerNorm 的区别是什么?
> **答:** LayerNorm: `(x - mean) / std * γ + β`,需要均值和方差,有偏置 β。RMSNorm: `x / RMS(x) * γ`,只用均方根,无偏置。RMSNorm 省掉 mean 计算和 β 参数,速度快 ~10-15%,效果基本无损,所以现代 LLM 都用 RMSNorm。
- RoPE 相比 learned absolute position embedding 的优势是什么?
> **答:** ① 天然编码相对距离(Q·K 只依赖 |i-j|,而非绝对位置);② 可外推到训练时没见过的长度(配合 YaRN 更好);③ 不增加可学习参数;④ Learned absolute 长度固定,超出即崩,且只编码绝对位置,对相对关系的建模隐式且弱。
- GQA/MQA/MHA 的区别和工程取舍是什么?
> **答:** MHA: Q=K=V 头数相同,表达力最强但 KV cache 最大。MQA: 所有 Q 头共享 1 个 KV 头,KV cache 最小但质量可能下降。GQA: 折中,多个 Q 头分组共享 KV 头(MiniMind: 8Q:4KV)。工程取舍核心是推理时 KV cache 显存 vs 模型质量——大规模部署时 KV cache 是瓶颈,GQA 是当前最佳平衡点。
- 为什么小模型里 vocab size 会显著影响参数量?
> **答:** Embedding 层参数 = `vocab_size × hidden_size`。MiniMind: 6400×768 = 490 万。如果用 Qwen2 的 151K 词表: 151643×768 = 1.16 亿,**仅 Embedding 就超过整个模型 (64M)**。小模型总参数少,Embedding 占比极高,所以词表必须精简。MiniMind 用 6400 词表正是这个原因。
## 工业要求
- 训练前先确定可复现环境:Python、CUDA、PyTorch、GPU 显存。
- 所有实验记录命令、git 状态、数据版本、模型参数、随机种子。
- 第一天不追求效果,只追求可运行、可定位问题。
- 任何训练前先做小 batch smoke test,避免长时间跑错配置。
## 项目中要看的文件
- `README.md`:快速开始、数据、训练说明。
- `model/model_minimind.py`:模型结构。
- `trainer/train_pretrain.py`:预训练入口。
- `trainer/train_full_sft.py`:SFT 入口。
- `dataset/lm_dataset.py`:数据到 tensor 的转换。
- `eval_llm.py`:训练后推理评测。
## 今日动手任务
1. 确认环境:
```bash
python - <<'PY'
import torch
print(torch.__version__)
print(torch.cuda.is_available())
print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else "no cuda")
PY
```
2. 阅读 `model/model_minimind.py` 中这些类:
- `MiniMindConfig`
- `Attention`
- `FeedForward`
- `MOEFeedForward`
- `MiniMindForCausalLM`
3. 写一段自己的结构笔记:输入 `[B, T]` 如何变成 logits `[B, T, vocab]`。
## 训练注意事项
- 今天不要直接跑完整训练。
- 如果没有 CUDA,后续训练计划必须缩小模型或只做代码理解。
- 如果用 Mac/MPS,混合精度、性能和部分算子兼容性要单独确认。