本文深入浅出地介绍了大语言模型(LLM)的原理、构建方法和实际应用。文章首先解释了LLM的基本概念和核心训练任务,即通过海量文本数据学习生成通顺的文本内容。接着,详细阐述了构建LLM的步骤,包括预训练和微调,并介绍了Transformer架构和注意力机制等关键技术。文章还提供了具体的代码实现和实验结果,展示了如何使用Python和PyTorch在普通电脑上构建和训练LLM。最后,总结了从零构建LLM的完整流程,强调了理解LLM工作原理的重要性。
这篇文章将让你对神秘莫测的大语言模型(LLM),从"这玩意儿到底怎么工作的?"到"哦,原来我也能造一个!"的奇妙转变。
咱们一起轻松愉快的发车吧!
第1章:大语言模型是什么鬼?
1.1 LLM 到底是啥?
LLM(Large Language Model),翻译成人话就是:一个特别大的神经网络,吃了海量的文本数据,然后学会了说人话。
但你千万别被骗了——所谓的"理解",其实是模型能生成看起来通顺、上下文相关的内容,它并没有人类的意识。就像鹦鹉会说"你好",但它并不真的想跟你打招呼。
LLM 的核心训练任务特别简单:预测下一个词。给你一句话的前半段,猜后半段。比如:
输入:“我今天中午吃了——”
输出:“饭”
就这么朴素的逻辑,居然能训练出 ChatGPT 这种怪物。这就像你教孩子就教了"1+1=2",结果他自己学会了微积分——研究者自己都觉得很惊讶。
1.2 LLM 能干啥?
翻译、写小说、写代码、做客服、总结文档、回答法律问题……基本上跟文字沾边的事它都能干。现在的 LLM 不仅仅是聊天机器人,它正在重新定义我们和技术的交互方式。
1.3 构建 LLM 的两步走
预训练(Pretraining)
用海量无标注文本训练模型做"填空",得到一个"基础模型"(foundation model)。这一步极其烧钱——GPT-3 的预训练花掉了460 万美元的云计算费用。
微调(Fine-tuning)
用少量标注数据进一步训练,让它变成某个领域的专家。又分两种:
自定义的小模型能在特定领域干翻通用大模型,而且还省钱、保护隐私、能部署在手机上。苹果公司就在搞这个。
1.4 Transformer 架构:LLM 的"祖宗"
2017 年的神论文《Attention Is All You Need》提出了 Transformer 架构,它有两个子模块:
编码器(Encoder):读入输入文本,编码成向量
解码器(Decoder):拿编码器的输出,生成目标文本
原始 Transformer 是用于翻译的,编码器读英文,解码器吐中文。后来的 BERT 用了编码器部分(擅长分类),GPT 用了解码器部分(擅长生成)。本书只做 GPT,也就是只用解码器。
1.5 海量数据集
GPT-3 的训练数据包括:
CommonCrawl(网络爬虫):4100 亿 token,占 60%
WebText2:190 亿 token
Books1 + Books2:670 亿 token
Wikipedia:30 亿 token
总共训练了 3000 亿 token。什么概念?如果一个人每秒读一个字,不吃不睡要读9.5 万年。
1.6 GPT 架构的精髓
GPT 本质上就是 Transformer 的解码器部分,它通过自回归的方式生成文本——每次预测一个词,然后把这个词拼回输入,再预测下一个词。
GPT-3 有96 层 Transformer、1750 亿参数。这些模型还展现出涌现能力(emergent behavior)——没专门学过翻译,但能翻译;没专门学过写诗,但能写。就像你本来想养条看门狗,结果它自己学会了弹钢琴。
1.7 三阶段构建计划
本书把构建 LLM 分为三个阶段:
| 阶段 | 内容 | 章节 |
|---|---|---|
| Stage 1 | 数据准备 + 注意力机制 | 第2-3章 |
| Stage 2 | 构建 LLM + 预训练 | 第4-5章 |
| Stage 3 | 微调(分类 + 指令) | 第6-7章 |
(图片来自gpt,image_prompt: A three-stage pipeline diagram showing Stage 1 (Data Preparation & Attention Mechanism), Stage 2 (GPT Architecture & Pretraining), and Stage 3 (Fine-tuning for Classification & Instruction Following), with arrows connecting each stage, colorful modern flat design style)
第2章:跟文本数据打交道——把"人话"变成机器能啃的饲料
2.1 词嵌入:给每个词发张"身份证"
神经网络不认识文字,只认识数字。所以我们需要嵌入(embedding)——把每个词映射成一串浮点数。
比如"猫"可能被映射成 [0.43, 0.15, 0.89],"狗"映射成 [0.41, 0.18, 0.85]。相似的词向量距离近,不相似的离得远。
GPT-2 Small 的嵌入维度是768,GPT-3 最大版是12,288。维度越高能捕捉的关系越细腻,但计算量也越大。这就好比用 4K 电视看《猫和老鼠》——画质再好,内容还是那只猫追那只老鼠。
2.2 分词:把句子大卸八块
分词(tokenization)就是把一段话切成小碎片。作者用了一篇叫《The Verdict》的短篇小说(20,479 个字符)做示例。
先用 Python 正则表达式手动实现分词器,处理了单词、空格、逗号、句号、问号、引号、双破折号等特殊情况。最终把这篇小说切成了4,690 个 token。
2.3 Token → Token ID:编号发牌
每个唯一 token 分配一个唯一整数 ID。作者实现了 SimpleTokenizerV1 类,有 encode(文本→ID列表)和 decode(ID列表→文本)两个方法。
但有个大问题:遇到没见过的词(比如"Hello"),直接报 KeyError。
2.4 特殊 Token:给 LLM 加点暗号
为了解决这个问题,引入两个特殊 token:
<|unk|>:表示"我不认识这个词"
<|endoftext|>:分隔不相关的文本片段
GPT 系列只用 <|endoftext|> 就够了,其他模型还会用 [BOS](开始)、[EOS](结束)、[PAD](填充)。
2.5 Byte Pair Encoding(BPE):GPT 的秘密武器
SimpleTokenizer 遇到未知词就变成 <|unk|>,太粗暴了。GPT 用的是BPE 分词器(来自 OpenAI 的 tiktoken 库)。
BPE 的牛X之处:不认识整个词?没关系,拆成子词甚至单个字符。所以 BPE永远不会遇到 unknown token——它总能把输入拆成它能处理的碎片。
GPT-2 的 BPE 词汇表有50,257 个 token。
2.6 滑动窗口采样:制造训练数据
LLM 的训练是预测下一个词,所以需要构造"输入-输出"对。
用滑动窗口在文本上移动:max_length 控制输入长度,stride 控制步长。比如 stride=1,输入是 [1,2,3],输出就是 [2,3,4]——每次往后挪一位。
从一篇短篇小说就能造出海量的训练样本。
2.7-2.8 Token 嵌入 + 位置编码
Token ID 经过 nn.Embedding 层变成向量(一个大查询表)。但嵌入层有个致命缺陷:不管词在句子的哪个位置,向量都一样。
解决方案是位置编码。GPT 用可训练的位置嵌入——每个位置有自己的向量,直接加到 token 嵌入上。位置 1 加位置 1 的向量,位置 2 加位置 2 的。简单粗暴但有效。
(图片来自gpt,image_prompt: A flowchart showing text processing pipeline: raw text → tokenization → token IDs → embedding layer → embedding vectors + positional encoding → final input vectors, modern flat design with bright colors)
第3章:注意力机制——让 LLM 学会"察言观色"
3.1 RNN 的"记忆缺陷"
在 Transformer 出来之前,RNN 是处理序列数据的主流。它的工作方式是把输入压缩成一个"记忆状态",然后基于这个状态做生成。
但 RNN记不住太长的东西。就像让你传话,传了十个人之后,原话早就走样了。
3.2 注意力的诞生
2017 年的 Transformer 论文提出自注意力(Self-Attention)机制——模型在生成每个词时,可以"回头看"输入序列中的所有位置,决定哪些地方更重要。
3.3 丐版自注意力
先看不带可训练权重的简化版本:
对于句子中每个词,计算它跟所有词的相关性(用点积衡量),然后 softmax 归一化成概率,再用这些概率加权求和所有词的向量——得到上下文向量。
这个上下文向量融合了整个句子的信息,解决了 RNN 的"健忘"问题。
3.4 完全体:缩放点积注意力
真正的 LLM 用的是带三个可训练权重矩阵的自注意力:
Wq(Query)
你在问"这段话里谁跟我有关系?"
Wk(Key)
每个词在说"我的特征是这些"
Wv(Value)
“如果觉得我重要,就拿走这个信息”
计算步骤:
输入分别乘 Wq、Wk、Wv 得到 Q、K、V
attn_scores = Q @ K.T算相关性
除以 √d_k
(缩放),防止高维度下点积过大导致梯度消失
softmax 归一化
output = attention_weights @ V得到上下文向量
3.5 因果注意力:不许"开天眼"
训练时,模型不能看到未来的词。比如预测 “I love you” 中的 “you” 时,只能看到 “I love”,不能偷看答案。
实现方式:在注意力权重矩阵的右上角加一个上三角掩码,把未来位置的权重全变成 0。方法是在 softmax 之前把右上角全设为 -inf,这样 softmax 后自然就是 0。
还引入了Dropout,随机丢弃一部分注意力权重(训练时用 0.1-0.2),防止过拟合。
3.6 多头注意力:一个脑袋不够用
多头注意力就是并行运行多个因果注意力机制。不同头关注不同信息——一个可能关注语法关系,另一个关注语义相似性。
GPT-2 Small(1.24 亿参数)有12 个头,嵌入维度 768,每个头处理 64 维。
高效实现方式:先做一次大 QKV 投影,然后拆分成多个头,用批量矩阵乘法并行计算所有头。
第4章:搭积木——实现 GPT 模型架构
4.1 GPT-2 的"身份证"
GPT-2 Small(1.24 亿参数)的配置:
| 参数 | 值 |
|---|---|
| 词汇量 | 50,257 |
| 上下文长度 | 1,024 |
| 嵌入维度 | 768 |
| 注意力头数 | 12 |
| Transformer 层数 | 12 |
| Dropout | 0.1 |
为什么用 GPT-2 不是 GPT-3?因为 GPT-3 的 1750 亿参数在单张 V100 上要训355 年。等你训完,世界末日可能都过去好几轮了。
4.2 Layer Normalization:情绪稳定剂
深度神经网络的梯度容易消失或爆炸。层归一化把每一层的输出拉成均值为 0、方差为 1 的标准分布。
LayerNorm 不依赖 batch size(不像 BatchNorm),所以特别适合 LLM 训练。
而且它还有可训练的 scale 和 shift 参数——如果模型觉得标准化后不好用,它能自己调回去。就像你妈让你把房间收拾整齐,但你之后可以把枕头摆歪一点——舒服就行。
4.3 GELU:比 ReLU 更"温柔"
ReLU 简单粗暴:正数留着,负数变零。
GELU 就温柔多了:负数区域也保留非零梯度,只是很小。ReLU 像"不及格就滚",GELU 像"不及格还可以补考"。
前馈网络(FeedForward)结构:Linear(768 → 3072) → GELU → Linear(3072 → 768)。先扩大 4 倍再缩小,维度没变但内涵丰富了。
4.4 Shortcut Connections:高速通道
随着网络加深,梯度在反向传播时会越来越小。**Shortcut(残差连接)**就是把输入直接加到输出上,给梯度修了一条"高速公路"。
没有 shortcut 时第一层比第五层梯度小 25 倍;加了 shortcut,各层梯度大小基本一致。
4.5 Transformer Block:组装成型
每个 Transformer Block 的结构:
LayerNorm → Multi-Head Attention → Dropout → Shortcut
LayerNorm → FeedForward → Dropout → Shortcut
注意这里用的是Pre-LayerNorm(先归一化再进子层),比 Post-LayerNorm 训练更稳定。
关键是:输入输出形状完全一样!所以你可以像叠罗汉一样叠 12 层。
4.6 GPTModel:终极合体
GPTModel = Token Embedding + Positional Embedding + 12× TransformerBlock + Final LayerNorm + Output Head(映射到 50,257 维词汇表)
1.63 亿参数,float32 存储需要 621 MB。跟今天 Llama 3 70B(存权重就要 140GB)比,简直是小巫见大巫。
4.7 文本生成
用 generate_text_simple 做自回归生成:输入 “Hello, I am”,输出 “Featureiman Byeswickattribute argue”。
这是啥玩意儿?别急——模型还没训练,权重是随机的,跟刚出生的婴儿一样,话都不会说。
第5章:预训练——从"胡言乱语"到"能说会道"
5.1 评估文本质量:怎么量化"说得好不好"?
交叉熵损失(Cross-Entropy Loss):模型预测的概率分布跟实际情况差多远。值越小越好。
困惑度(Perplexity)= exp(loss)。初始值 48,725,相当于模型在 50,257 个词里完全随机乱猜。理想目标:接近 1。
训练数据集:《The Verdict》(只有 5,145 个 token),90% 训练 / 10% 验证。
5.2 训练过程:见证 AI 的成长
用 AdamW 优化器,训练 10 个 epoch(约 5 分钟):
| Epoch | Loss | 生成文本 |
|---|---|---|
| 1 | 9.78 | Every effort moves you, (变逗号复读机) |
| 2 | 6.66 | Every effort moves you, and, and, and, … ("and"复读机) |
| 9 | 0.54 | 开始输出语法正确的句子 |
| 10 | 0.39 | 能写出通顺的英文句子 |
从逗号复读机到能写出完整句子——这 5 分钟的学习曲线比某些大学生四年的进步还明显。
但验证 loss 在第 2 个 epoch 后就停滞在 6.45 了,而训练 loss 降到了 0.39——过拟合了。解决办法:用更大的数据集。
5.3 解码策略:让模型有创意而不是背答案
贪心解码(Greedy Decoding):每次选概率最高的 token。安全但无聊,像每次都点同一道菜。
Temperature 缩放:控制随机性。
temperature = 0.1:几乎确定,像严谨的科学家
temperature = 1:正常发挥
temperature = 5:放飞自我,有 4% 概率生成 “Every effort moves you pizza”
Top-k 采样:只保留概率最高的 k 个 token 再采样。k=3 时,“forward、toward、closer” 竞争,“pizza” 直接出局。
把两者结合:先用 top-k 筛选候选池,再用 temperature 调整概率分布后再采样。
5.4 保存和加载模型
保存
torch.save(model.state_dict(), "model.pth")加载
model.load_state_dict(torch.load("model.pth"))如果要继续训练,记得连 optimizer 状态一起保存——AdamW 记住了每个参数的历史学习率,丢了就失忆了。
5.5 白嫖 OpenAI 的预训练权重
自己训太贵?直接用 OpenAI 的 GPT-2 权重!下载 498MB 的权重文件,需要做一个"翻译"工作——OpenAI 的变量命名和我们不一样,得手动映射。
加载成功的标志:输入 “Every effort moves you”,不再输出乱码,而是输出 “toward finding an ideal new way to practice something!”——这才是正常人该说的话。
(图片来自gpt,image_prompt: Illustration showing pretraining process: unlabeled text data flowing into a GPT model, the model predicting next tokens, loss curve decreasing from high to low over epochs, and the output changing from gibberish to coherent sentences, colorful educational style)
第6章:分类微调——让 LLM 当"专才"
6.1 微调的两种路子
分类微调
让模型变成"偏科生",只会做一件事(比如判断垃圾邮件)
指令微调
培养"全才",能听懂各种指令
全才比偏科生难训练得多。作者说得很直白:全才难做,偏科容易。
6.2 准备垃圾邮件数据集
用SMS Spam Collection数据集(5,572 条短信)。原始数据不平衡(垃圾 747 vs 正常 4,825),用欠采样平衡到 747 vs 747。
数据集按 70% 训练、10% 验证、20% 测试拆分。
6.3 创建数据加载器
短信长短不一,用 <|endoftext|> token 填充到一样长。每个 batch 内所有序列统一长度。
6.4 加载预训练权重
加载 GPT-2 预训练权重。试一下未经微调的模型:你问它"这是垃圾邮件吗?",它不回答,只会继续补全文本——光预训练不微调,模型就是个复读机。
6.5 加装分类头
把原来的输出层(映射到 50,257 个 token)换成新的(映射到 2 个类别:spam / not spam)。
技巧:
冻结大部分参数
(设置 requires_grad = False),只训练输出层 + 最后一个 Transformer block + 最后的 LayerNorm
只用最后一个 token 的输出做分类
——因为因果注意力掩码让最后一个 token 能看到前面所有信息
6.6 训练结果
超参数:AdamW,学习率 5e-5,weight_decay 0.1,5 个 epoch。
在 M3 MacBook Air 上跑约5.65 分钟:
| 指标 | 数值 |
|---|---|
| 训练集准确率 | 97.21% |
| 验证集准确率 | 97.32% |
| 测试集准确率 | 95.67% |
测试集 95.67%,相当能打。而且训练和验证曲线贴得很近——没有过拟合,nice。
6.7 保存模型
torch.save(model.state_dict(), "classifier.pth")第7章:指令微调——让 LLM 当"通才"
7.1 目标
把 GPT-2 medium(3.55 亿参数)训练成一个能听懂人话指令的模型。“把这句话改成被动语态”、“伦敦的首都是哪里”——这些活儿它都得会干。
跟分类微调不同,指令微调不限制输出——输出空间大得没边,难度高得多。
7.2 准备指令数据集
1,100 条指令-回答对(JSON 格式),每条包含 instruction、input、output 三个字段。
使用Alpaca 格式组织 prompt:
Instruction: 把下句改成被动语态
Input: The chef cooks the meal.
Response: The meal is cooked by the chef.
数据集划分:85% 训练(935 条)、10% 测试(110 条)、5% 验证(55 条)。
7.3 自定义 Collate 函数
指令长度不一,需要每个 batch 各自填充到当前 batch 的最长长度,而不是整个数据集的最长长度。这样节省大量计算和内存。
关键技巧:把 padding token 在 target 中对应的位置设为-100。PyTorch 的交叉熵损失默认忽略 -100,这样 padding 部分就不会参与损失计算。不然模型会去学预测那些无聊的填充 token。
7.4 加载 GPT-2 medium(355M)
为什么用 3.55 亿参数而不是 1.24 亿?因为指令微调任务复杂,小模型撑不住。
没微调的模型试一下:让它把主动句改被动句——它直接复读原句,还顺带重抄了一遍指令。就像学生考试时把题目抄了一遍当答案。
7.5 正式微调
超参数:AdamW,学习率 5e-5,只跑 2 个 epoch(数据集小,再多就过拟合了)。
在 A100 GPU 上只需52 秒。训练 loss 从 2.637 降到 0.300。
第 1 个 epoch 结束:已经能把 “The chef cooks the meal every day.” 改成 “The meal is prepared every day by the chef.”——虽然用了 “prepared” 而不是标准答案的 “cooked”,但语法和语义完全正确。
第 2 个 epoch 结束:变成正确的 “The meal is cooked every day by the chef.”
7.6 评估
人工评估 110 条回复太累?用另一个 LLM(Llama 3 8B)来打分!
评分 prompt 里包含:指令 + 标准答案 + 模型回答,让 Llama 3 从 0-100 打分。
测试样例:
“快如子弹”(标准答案是"快如闪电")→85 分(没毛病但不够精确)
“积云”(标准答案是"积雨云")→40 分(方向对了但答错了)
“简·奥斯汀” →95 分(接近完美,但稍微啰嗦了点)
110 条测试集平均分:50.32 分。可以用来做基准对比。
假如你从2026年开始学大模型,按这个步骤走准能稳步进阶。
接下来告诉你一条最快的邪修路线,
3个月即可成为模型大师,薪资直接起飞。
阶段1:大模型基础
阶段2:RAG应用开发工程
阶段3:大模型Agent应用架构
阶段4:大模型微调与私有化部署
配套文档资源+全套AI 大模型 学习资料,朋友们如果需要可以微信扫描下方二维码免费领取【保证100%免费】👇👇