首先大模型不是凭空出来的,它都是基于Transformer架构的。
Transformer架构不是凭空出现的,它是去掉了RNN的循环结构,仅保留注意力机制,对RNN的改进重构出现的。
RNN也不是凭空出现的,是对全连接神经网络的改进出现的。
神经网络是多个逻辑回归的组合,逻辑回归又基于线性回归。
而要学懂这些,你还需要一些必备的数学基础。
作者:小萌 链接:https://www.zhihu.com/question/621550974/answer/1935263681278608637 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1.搞懂微小的模型,几个参数再到几十个参数的模型,比如感知机,或者反向传播网络BP。这里重点要搞懂模型参数是什么?搞懂训练过程,搞懂损失函数(L2,交叉熵)搞懂梯度下降法,搞懂推理过程。这里基本搞清楚了机器学习中的“学习”到底是什么,以后不管模型多大,使用的“学习”都是一样。
2.然后接着搞懂RNN,连LSTM都不用搞。就把RNN搞清楚就行。搞懂递归神经网络其实就是把一个bp反复用而已。至于模型的训练策略,损失函数,推理过程是跟bp类似。RNN在拓扑结构上类似两个bp并联然后再串联一个bp.
-
接着就看transformer,关注一下:与rnn相比,为啥transformer可以不再递归?不再递归以后,没有顺序信息了,序列长度不固定的问题,解码器训练时的样本组织问题等都是怎样打补丁解决的?你会发现所谓的自注意力其实还是BP的变形,整个transformer虽然分解码器,编码器,再具体的,虽然分自注意力模块,前馈网络模块,但基本计算单元还是类似bp中的隐藏层。transformer在拓扑结构上,就是大概40个bp的复杂串并连。
-
再看Bert,gpt1,2,3,4,llama你就会发现网络结构与transformer相比没有创新。Bert的最重要创新点在于更改了输入输出,更改了恢复顺序信息的办法,实现了训练数据自由,所以叫无导师,从而实现更大规模的,更多损失函数的训练,从而训练出了更好的transformer解码器。GPT创新点就是大量的增加解码器,编码器的层数,原理真的就仅仅是大力出奇迹而已,以至于实现了所谓的零学习,单例学习,或者元学习,使人工智能看起来不再像个智障,回答问题差不多靠谱了。当然原理上大力出奇迹,但工程量大,优化需求旺盛。
总之,三层反向传播网络没学好,大模型就理解不了,也学不好。三层反向传播网络里的细节搞清楚了,稍微大点的模型就真的很容易,只要直接看源码,就很容易搞清楚作者论文里提出的各种新名词,就比如自注意力,多头自注意力等,都是三层反向传播网络的串并连而已,只不过不同位置的隐藏层发挥作用不同,所以有的叫词嵌入,有的叫QKV注意力等。总之,不被这些新名词迷惑住,还会觉得作者们真的很会玩概念,起名字。
作者:赵明明 链接:https://www.zhihu.com/question/621550974/answer/3430393581 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
-
Andrej 手写 GPT 教程
-
RethinkFun深度学习
-
CS336: Language Modeling from Scratch
-
大规模语言模型:从理论到实践
-
从零开始的大语言模型原理与实践教程
标准 Multi-Head Attention(MHA):Q、K、V 各有 n_heads 个头,每个头独立做 softmax(QK^T / sqrt(d)) · V。你需要理解为什么朴素实现会产生 O(n²) 的中间矩阵,以及为什么 FlashAttention 能通过 tiling 和 online softmax 避免这个中间矩阵的显存开销——这是目前最重要的 Attention kernel 优化技术。
Multi-Query Attention(MQA):所有 Q 头共享一组 K 和一组 V。这意味着 KV Cache 大幅缩小,对推理时的显存和带宽压力影响巨大。
Grouped-Query Attention(GQA):MHA 和 MQA 的折中,比如 Llama 3 用 8 个 KV 头对 32 个 Q 头。Kernel 实现时需要处理 Q 头到 KV 头的映射关系,不再是简单的一对一。
Multi-Latent Attention(MLA):DeepSeek V2/V3 引入的,把 KV 投影到低维潜空间再存储,进一步压缩 KV Cache。这是更前沿的知识。
按照"每个模型引入了什么你必须知道的架构创新"来筛选,只列真正绕不开的:
第一梯队:必须深入理解
Transformer(2017,Google)——一切的起点。原始论文 "Attention Is All You Need" 定义了 Self-Attention、Multi-Head Attention、位置编码、残差连接、LayerNorm 这些基本组件。不需要背公式,但要理解每个组件的计算流和张量形状。
GPT-2(2019,OpenAI)——Decoder-only Transformer 的标准参考实现。架构极其干净:Pre-LayerNorm、标准 MHA、两层 FFN + GELU。Andrej Karpathy 的 nanoGPT 就是基于 GPT-2 写的,很多 kernel 工程师的入门就是从读 nanoGPT 的代码开始。它的价值不在于性能,而在于它是理解所有后续模型的"基准线"——后面的模型都是在它基础上改东西。
Llama 1(2023,Meta)——现代 LLM 的"标准模板"。相比 GPT-2,它引入了三个关键变化:RMSNorm 替代 LayerNorm、RoPE 替代学习型位置编码、SwiGLU 替代 GELU FFN。这三个改动如今已经成为几乎所有主流 LLM 的默认配置。如果你只深入研究一个模型的完整实现,选 Llama 1 就对了。
第二梯队:必须了解架构差异
Llama 2 70B(2023,Meta)——架构上与 Llama 1 高度一致,但 70B 版本首次在 Llama 系列中引入了 GQA(8 个 KV 头对 32 个 Q 头)。注意 Llama 2 的 7B 和 13B 仍然使用标准 MHA,与 Llama 1 架构相同。GQA 的引入意味着 Q 头和 KV 头的数量不再对称,对 kernel 工程师来说,Attention kernel 和 KV Cache 管理的实现都要处理这种不对称性。
Llama 3(2024,Meta)——将 GQA 变成全系列的默认配置(包括 8B),并大幅扩展词表到 128K。从 Llama 3 开始,GQA 成为主流 LLM 的标配,必须会。
Mixtral 8x7B(2023,Mistral)——开源界最有影响力的 MoE 模型。你需要理解 router/gate 怎么把 token 分发给不同 expert,以及由此带来的不规则计算(每个 expert 接收的 token 数不同)和 Expert Parallelism 下的 All-to-All 通信。MoE 的 kernel 优化和 dense 模型差异很大。
第三梯队:了解其创新点即可
GPT-1(2018,OpenAI)——历史意义大于实际参考价值。它确立了"预训练 + 微调"的范式和 decoder-only 架构,但具体实现已经被 GPT-2 完全覆盖。知道它的定位就行,不需要深入代码。
BERT(2018,Google)——Encoder-only 架构,bidirectional attention。做 LLM 推理 kernel 的人不太会直接碰到它,但理解 encoder 和 decoder 的 attention mask 区别(full attention vs causal attention)是基本功。
GPT-3(2020,OpenAI)——架构上和 GPT-2 几乎一样,只是大了 100 倍。它的意义在于验证了 scaling law,但对 kernel 工程师来说没有引入新的计算模式。不过它让人们开始认真思考大规模 GEMM 的效率问题。
DeepSeek V2/V3(2024,深度求索)——引入了 MLA(Multi-Latent Attention),把 KV 投影到低维空间来压缩 KV Cache。如果你做推理优化,这是需要关注的前沿方向。另外 DeepSeek 在 2025 年开源周放出的 FlashMLA、DeepGEMM 等 kernel 实现本身就是非常好的学习材料。
Mistral 7B(2023,Mistral)——引入了 Sliding Window Attention,限制每个 token 只关注局部窗口内的上下文。这改变了 Attention 的计算范围和 KV Cache 的管理策略。
实际学习路径建议:
先把 GPT-2 的完整前向过程搞透(推荐读 nanoGPT 的代码),然后切到 Llama 1 理解现代 LLM 的三件套改动(RMSNorm、RoPE、SwiGLU),再看 Llama 2 70B/ Llama 3 理解 GQA,最后看 Mixtral 理解 MoE。这几个模型吃透了,你遇到市面上任何新模型都能快速定位它改了什么、对 kernel 有什么影响。
结合这份指南,我为你定制了一条更偏向体系结构和底层工程的落地路径:
Phase 1: 筑基 (理解计算与内存架构)
- 读透 Transformer 原理 -> 手写一个最慢的、Naive 的 Attention Kernel (CUDA) -> 读懂 GPT-2 /
llm.c。
Phase 2: 现代工程标准 (算子融合与访存优化)
- 理解 Llama 1 的三件套 -> 精读 FlashAttention 论文 -> 尝试用 Triton 或 CUDA 实现一个简易版的 FlashAttention。
Phase 3: 解决系统级瓶颈 (带宽与通信)
- 研究 Llama 3 (GQA) -> 结合 vLLM 源码理解 PagedAttention 是如何管理不连续内存的 -> 研究量化 (W4A16),写一个带反量化逻辑的 GEMV Kernel。
Phase 4: 挑战前沿 (稀疏性与动态调度)
- 研究 Mixtral 8x7B (MoE 路由逻辑) -> 拆解 DeepSeek V3 的开源 Kernel (如 DeepGEMM 或 FlashMLA),看看目前工业界是如何榨干 Hopper 架构算力的。
-
Hugging Face Transformers
-
DeepSpeed-Inference
-
vLLM
-
SGLang
-
Hugging Face TGI
-
NVIDIA TensorRT-LLM
-
DeepSpeed
-
NVIDIA Megatron-Core / Megatron-LM
原始互联网文本
↓
├── Pre-training ← 阶段 1:让模型"会说话"
↓
Base 模型 ← 比如 LLaMA 2 7B(不能聊天)
↓
├── SFT ← 阶段 2:让模型"会聊天"
↓
SFT 模型
↓
├── RLHF / DPO ← 阶段 3:让模型"对齐人类偏好"
↓
Chat 模型 ← 比如 LLaMA 2 7B Chat
Post-training = "Pre-training 之后所有的训练阶段" 的总称。包括但不限于:
- SFT(监督微调)
- RLHF(基于人类反馈的强化学习)
- DPO(直接偏好优化)
- 各种针对性的 fine-tuning(比如代码能力、数学能力专门训练)
注意,base 模型和 chat 模型架构和模型参数量完全相同,只要有参数的具体值是不一样的。
- 目标:在海量文本上学"语言本身"——就是 "next token prediction"。
- 数据:互联网文本(CommonCrawl、Wikipedia、Books、Code 等)——没有任何标注。
- 任务:给定前面的 token,预测下一个 token。
- 结果:得到一个"会说话但不会聊天"的 base 模型。
- 典型规模:
- LLaMA 2 7B:训练了 2 万亿 token
- 用了几千张 GPU、几个月
- 烧了几百万美金
Base 模型的特征:
如果你直接给 LLaMA 2 7B base 一个问题:输入: "What is the capital of France?"
它可能续写:
"What is the capital of France? What is the capital of Germany?
What is the capital of Italy? Here are 50 capital city quiz questions..."
它不知道"应该回答你"——它只是把你的问题当作一段文本的开头,继续往下写。这就是 base 模型——像高中生一样能补全任何文本,但不会"聊天"。
-
目标:教会 base 模型"对话格式"——看到用户问题,应该输出回答。
-
数据:人工编写的高质量对话数据。
{ "input": "What is the capital of France?", "output": "The capital of France is Paris." }通常几万到几十万条这种数据——全是人工写的。
-
训练方式:还是 next token prediction——只是数据从"互联网文本"换成了"标准对话格式"。
具体说——把每条数据格式化为:
<|user|> What is the capital of France? <|assistant|> The capital of France is Paris.模型学习"看到 user 输入后该输出 assistant 回复"——并且学会输出 EOS 停下来。
-
结果:模型现在会聊天了——但回答质量参差不齐——有时候有用、有时候啰嗦、有时候编造事实。
-
目标:让模型的回答对齐人类的偏好——回答得"好",不只是"格式对"。
-
核心问题——什么叫"好的回答"?这没有标准答案,但人类能判断哪个更好。
-
RLHF 三步
-
1:收集偏好数据
让人类给同一个问题的多个回答排序:
问题: "怎么做番茄炒蛋?" 回答 A: "把番茄切块,蛋打散,先炒蛋..." ← 人类觉得:好 回答 B: "你应该问厨师。" ← 人类觉得:差 回答 C: "番茄炒蛋是中国菜..." ← 人类觉得:中等 偏好数据:A > C > B收集几万到几十万条这种偏好对。
-
2:训练 Reward Model(奖励模型)
用偏好数据训练一个奖励模型——它能给任何回答打分:
Reward Model: 输入: (问题, 回答) 输出: 一个分数("这个回答有多好")奖励模型本身也是个 Transformer——它学的是模仿人类的偏好。
-
3:用强化学习优化
用 Reward Model 作为"老师"——通过强化学习(通常用 PPO 算法)调整 SFT 模型的参数——让它生成的回答得到更高的奖励。
这就是 RLHF 的核心:
- SFT 模型生成回答
- Reward Model 打分
- 强化学习算法根据分数更新 SFT 模型
-
-
结果:模型回答变得更有用、诚实、无害(OpenAI 的"3H 原则")——因为它学会了人类喜欢什么样的回答。
-
RLHF 的简化版——2023 年提出。
-
RLHF 的痛点:流程复杂——需要训练 Reward Model、配置强化学习、调超参数——工程难度大。
-
DPO 的思路:跳过 Reward Model——直接用偏好数据微调模型。
数学上证明了——可以推出一个直接基于偏好对的损失函数——不需要显式的 Reward Model。
-
DPO 的优势:
- 实现简单(就是微调,不需要 RL 框架)
- 训练稳定
- 效果和 RLHF 接近
LLaMA 2 用的是 RLHF——LLaMA 3 之后很多模型切到了 DPO。
class DecoderOnlyTransformer:
def forward(self, token_ids):
x = embedding(token_ids)
for _ in range(N): # 重复 N 次
x = x + attention(norm(x)) # masked self-attention + 残差
x = x + ffn(norm(x)) # FFN + 残差
x = norm(x)
return linear(x) # 输出 logits原版 Transformer 论文("Attention Is All You Need")的描述里——WQ,WK,WV,WO 严格说就是矩阵乘法,没显式写 bias。当然 FFN 部分是有两个 bias 的。
但是,实际代码实现中经常带 bias:
-
PyTorch 的 nn.Linear:
nn.Linear(in_features, out_features, bias=True) # 默认 bias=True如果你写 attention 投影时没指定 bias=False——就默认带 bias了。
-
HuggingFace 的 BERT 实现
self.query = nn.Linear(config.hidden_size, self.all_head_size) # bias 默认为 True,BERT 的 Q/K/V 投影是带 bias 的 -
HuggingFace 的早期 GPT-2 实现:
self.c_attn = Conv1D(3 * config.n_embd, config.n_embd) # Conv1D 内部也是默认带 bias 的
为什么会有这种差异?
-
工程惯性:
nn.Linear 的默认值是 bias=True——程序员写代码时如果没特别注意,自然就带上了。没人特意去掉。
-
bias 通常无害:
带不带 bias 在小模型上几乎没区别——加上也不会让模型变差。所以早期实现都"宁可加上"。
-
论文原意:
如果你逐字读 Vaswani 等人的论文——WQ 等就是纯矩阵乘——他们大概率没特别考虑 bias 这件事。这是论文表述的简化。
真正去掉 bias 是什么时候开始的?LLaMA 1(2023) 是第一个**明确强调"全部去掉 bias"**的旗舰大模型——
- 所有 attention 投影没 bias
- 所有 FFN 投影没 bias
- 整个模型没有任何 bias 项
为什么 LLaMA 选择去掉?
- 省参数:每个 Linear 层省一个 bias 向量(虽然小)
- 省内存:减少访存量
- 简化实现:代码更干净
- 不影响效果:实验证明去掉 bias 性能几乎不掉
LLaMA 之后——Qwen、DeepSeek、Mistral 等几乎所有现代大模型都跟进去掉了 bias——这成了新的"事实标准"。
LLaMA 2 Chat 用的特殊 token:
<s>— BOS(句子开始)</s>— EOS(句子结束)[INST]— 用户输入开始[/INST]— 用户输入结束<<SYS>>— 系统提示开始<</SYS>>— 系统提示结束
用户问:"What is the capital of France?"
预先设置的 system prompt:"You are a helpful AI assistant. You must only provide positive feedback."
实际喂给模型的字符串:
<s>[INST] <<SYS>>
You are a helpful AI assistant. You must only provide positive feedback.
<</SYS>>
What is the capital of France? [/INST]
模型生成:
Paris is the wonderful capital of France! </s>
用户继续问:"What's Paris famous for?"
实际喂给模型的字符串(包含完整历史):
<s>[INST] <<SYS>>
You are a helpful AI assistant. You must only provide positive feedback.
<</SYS>>
What is the capital of France? [/INST] Paris is the wonderful capital of France! </s><s>[INST] What's Paris famous for? [/INST]
注意几个细节——
- 每一轮 user-assistant 都被一对
<s>...</s>包裹(多轮对话不只有一对 BOS/EOS) - system prompt 只在第一轮的
[INST]内——后续轮不重复 - assistant 的回复直接接在
[/INST]后面——没有专门的 "Assistant:" 标签
字符串经过 tokenizer——所有特殊 token 变成对应的 ID:
# 简化示意(实际 ID 因 tokenizer 而异)
"<s>" → 1
"[INST]" → 518, 25580, 29962 (拆成几个 token,不是单个特殊 token)
"<<SYS>>" → ...
"</s>" → 2注意——[INST] 这种"标签"在 LLaMA 2 里实际上不是单个 special token——是普通的字符串 token,会被 tokenizer 拆成几个 token。
但 <s> 和 </s> 是真正的 special token——单个 token ID。
LLaMA 2 用的 [INST] 风格在 2023 年很流行——但现代模型(GPT-4、Claude、Qwen、LLaMA 3 等)大多切到了 ChatML 格式。
ChatML 长这样——
<|im_start|>system
You are a helpful AI assistant.
<|im_end|>
<|im_start|>user
What is the capital of France?
<|im_end|>
<|im_start|>assistant
Paris.
<|im_end|>
<|im_start|>user
What's Paris famous for?
<|im_end|>
<|im_start|>assistant
注意:
<|im_start|>和<|im_end|>是真正的 special token(单 token ID)- 每个角色(system/user/assistant)有自己的"段落"
- 最后留一个
<|im_start|>assistant让模型知道"现在轮到你说话了" - 模型会一直生成直到吐出
<|im_end|>——表示这一轮回答结束
ChatML 比 LLaMA 2 的 [INST] 格式更清晰、更结构化——所以成了新标准。
1. 第 1 轮:
input_string = format_chat([
{"role": "system", "content": "You are a helpful AI assistant..."},
{"role": "user", "content": "What is the capital of France?"}
])
# 套上 ChatML(或其他模型特定格式)
input_ids = tokenizer.encode(input_string)
output_ids = model.generate(input_ids)
response_1 = tokenizer.decode(output_ids)2. 第 2 轮:
input_string = format_chat([
{"role": "system", "content": "You are a helpful AI assistant..."},
{"role": "user", "content": "What is the capital of France?"},
{"role": "assistant", "content": response_1}, # 上一轮的回答
{"role": "user", "content": "What's Paris famous for?"} # 新问题
])
input_ids = tokenizer.encode(input_string)
output_ids = model.generate(input_ids)
response_2 = tokenizer.decode(output_ids)每轮对话——整个历史重新拼接、重新 tokenize、重新喂进模型。
如果每轮都把整个历史重新喂进去——前面的 token 不是要重新 prefill 一遍吗?非常浪费。
这就是为什么实际部署时会用 KV cache 复用:
- 第 1 轮 prefill 完后,KV cache 里存了所有第 1 轮内容的 K/V
- 第 1 轮 decode 完成后,KV cache 里又追加了 assistant 回复的 K/V
- 第 2 轮开始时——KV cache 还在,不需要重新 prefill 之前的内容
- 只需要把新的 user 输入("What's Paris famous for?")喂进去做 prefill
- 然后正常 decode
所以实际上——多轮对话的"prefill"只覆盖每一轮新增的部分,不是每次都从头算。
但 vLLM、HuggingFace 的 generate() 等系统对这个的支持各有不同:
- 简单实现(HF generate 默认):每轮都重新 prefill 整个历史——浪费但简单
- 优化实现(vLLM、TGI、Anthropic 内部):跨轮复用 KV cache——省 prefill 时间
这是 LLM 推理服务的一个核心优化——叫 "prefix caching" 或 "KV cache reuse"。
你可能想,"如果 KV cache 跨轮复用——那每个对话都要在 GPU 上保留一份 cache?这内存怎么够?"
这就是 vLLM 的 PagedAttention 解决的问题:
- KV cache 像虚拟内存一样分页管理
- 不同对话共享 GPU 显存池
- 不活跃的对话的 cache 可以被换出/回收
这是为什么 vLLM 能高效服务几千个并发对话:靠精细的 KV cache 内存管理。
预测下一个 token 到底是怎么做到的?是 attention + FFN 协同 + 最后的 LM head 共同完成的。
首先,我们需要明确一下 decoder-only transformer 各结构的作用:
-
Attention(决定什么信息流向哪个位置)
让每个 token 的表示融合上下文信息,"我看了我以及我之前的所有 token,吸收了对我有用的部分"
最后一个 token 的输出向量包含了整段上下文的信息(因为 causal mask 让它能看到所有前面的 token)
-
FFN(加工和存储知识)
FFN 做两件事:
- 非线性变换:把"上下文化的混合表示"通过 SiLU/SwiGLU 加工成更"非线性"的形态
- 知识查询:可解释性研究发现——FFN 实际上在做 key-value 查询(你之前学过这个视角)——存储着模型从训练数据中学到的"事实性知识"
把"上下文化的混合"加工成"适合下一步的形态"
-
多层堆叠
重复 attention + FFN——让信息逐层精炼
-
Final RMSNorm + LM head + Softmax
把最后一层最后一个 token 的向量投影到词表概率分布,选出下一个 token。
这一步才是真正在做预测。
总结起来就是:
-
Attention + FFN(× 32 层)把"上下文信息"精炼成一个 4096 维的向量
想象每一层 attention + FFN 都在"调整指南针的方向"——
- 第 1 层后:指南针从"原始 token"方向转向"考虑了局部上下文"的方向
- 第 2 层后:指南针进一步转向"考虑了更广上下文"的方向
- ...
- 第 32 层后:指南针指向"在词表空间里,下一个 token 应该是的那个方向"
真正的推理工作在 attention + FFN 的反复交替中完成,它们一起把"原始上下文"逐层加工成"指向正确答案的向量"。
-
这个向量"指向词表空间里的某个方向"
-
LM head 找出"和这个方向最对齐的 token embedding"
LM head 只是"读取"这个指南针,找最近的词表 token,它本身不做推理,只是"翻译"。
-
那个 token 就是预测结果