LLM 基础(概念版):从“为什么”到“怎么跑在 mini-LLM 里”
1. 先定主问题:模型到底在学什么
Decoder-only Transformer 在学的是条件分布:
概念上,它不是在“背答案”,而是在每个位置做“下一词预测”。
训练时不断修正参数,让正确词的概率更高,错误词的概率更低。
在本项目的 Which-Span 任务里,序列有结构 token([SA] [EA] [SB] [EB] [QA]/[QB]),监督主要集中在答案区间,这让模型把能力聚焦在“根据问题挑 span”。
2. 从 token 到语义向量:为什么必须有 Embedding
token id 本质是离散编号,网络不能直接对“编号大小”做语义计算。
Embedding 把离散 id 映射到连续向量空间:
直觉:
- id
7和8的数值接近,不代表语义接近; - 经过 embedding 后,“语义相近 token”可被训练到向量上更接近;
- 后续所有线性层/attention 都在这个连续空间里运算。
代码对应:
weights.token_embeddingweights.output_projectionsrc/engine/embedding.cpp
3. 一层 Decoder 在做什么(过程视角)
把一层看成四步最清晰:
- 归一化后做 QKV(并加 RoPE)
- attention 聚合上下文 + 输出投影 + 残差
- 再归一化后过 SwiGLU MLP
- down projection + 残差
这和 src/model/executor_forward.cpp 的执行块一一对应。
4. Attention 为什么是“信息路由”
4.1 公式
4.2 概念解释(重点)
- (query):当前位置“想找什么信息”。
- (key):每个历史位置“我能被怎样匹配”。
- (value):每个历史位置“我提供的内容向量”。
所以注意力不是简单平均,而是:
- 先用 计算“匹配分数”;
- softmax 把分数变成概率权重;
- 用这些权重对 做加权和,得到上下文表示。
这正是“信息路由”:当前 token 动态决定从哪些历史 token 取信息、取多少信息。
4.3 为什么要除以
若不缩放, 的方差会随维度增大而变大,softmax 容易过饱和(接近 one-hot),梯度变差。
除以 是把分数拉回更稳定区间,便于训练。
4.4 因果 mask 在保证什么
把未来位置置为极小值,softmax 后近似 0。
这保证第 个位置只能看 的 token,符合自回归生成定义,避免训练-推理不一致。
5. RoPE 为什么对“相对位置”有效
RoPE 对每个二维子空间做旋转(只展示单对维度):
直觉不是“给向量加位置”,而是“按位置旋转向量方向”。
当 和 都做了对应旋转后,它们的内积会主要依赖位置差 的结构,从而自然编码相对位置信息。
为什么这对推理友好:
- decode 时每次只新增一个位置,旋转可独立算;
- 与 KV cache 天然兼容,不需要重写整段历史表示。
本项目里通常只旋转前 rope_dim 维,是计算和效果之间的折中。
6. GQA 在解决什么工程问题
维度关系:
- query 头数:
- key/value 头数:,且常见
- 约束:
GQA 的本质是“多个 query 头共享较少的 KV 头”。
这会降低 KV cache 占用和内存带宽压力,对长上下文 decode 很关键。
代价是表示自由度有一定下降,所以是典型的“速度/内存/容量”折中。
7. 为什么是 Pre-Norm + 残差(而不是 Post-Norm)
本项目采用:
7.1 残差在做什么
残差提供一条“恒等捷径”,即使子层学得不好,信息和梯度仍可直接通过主干。
这让深层网络更不容易梯度消失。
7.2 Pre-Norm 为什么常更稳
先 norm 再进子层,意味着每层子模块看到的输入尺度更可控。
训练早期参数剧烈变化时,这能减少数值抖动,通常比 post-norm 更容易稳定收敛。
8. RMSNorm 的角色:控制尺度,不改语义方向
概念上它主要做两件事:
- 统一不同 token 激活的尺度;
- 通过 保留可学习重标定能力。
它不像 LayerNorm 那样减均值,计算更轻,在 Transformer 中通常足够稳定。
9. SwiGLU MLP 在补什么能力
直觉:
- 是“内容流”;
- 是“软门控”;
- 逐元素乘法决定每个通道放行多少内容。
相比普通 Linear -> 激活 -> Linear,门控机制对信息选择更细粒度,通常更有表达力。
10. 形状一致性为什么是第一性原则
在实现里,很多 bug 不是“公式错”,而是“维度契约被破坏”。
常见硬约束:
- 的头数是
- 的头数是
o_proj要把多头拼接后再映射回
本项目通过 engine/validation.* 和模型构造期检查守住这些边界。
11. 参数量怎么读才有意义
近似写法:
意义不是背数字,而是理解“参数都花在哪”:
- 层数 决定深度容量;
- 每层 attention+mlp 决定主计算量;
- 来自输入 embedding 和输出投影(本项目分开存)。
12. 从“公式”映射到“源码”
- 类型与权重:
src/model/model_types.h - 前向编排:
src/model/executor_forward.cpp - 反向编排:
src/model/executor_backward.cpp - 模型级 API:
src/model/mini_llm.cpp - 具体算子:
src/kernel/*
建议阅读顺序:
- 先按本文件理解“每块在解决什么问题”;
- 再进
executor_forward.cpp对照执行顺序; - 最后看 backward,建立“前向块 -> 反向块”的镜像关系。