首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >手撕 GPT#02:从乱码到说人话,模型经历了什么?

手撕 GPT#02:从乱码到说人话,模型经历了什么?

作者头像
烟雨平生
发布2026-05-25 11:53:48
发布2026-05-25 11:53:48
780
举报

上一篇文章我们在 CPU 上训练了一个能回答中文问题的 GPT

训练完,问它"什么是注意力机制?",它回答:

"注意力机制通过计算查询和键的相关性分配权重,让模型动态关注最相关的部分。"

答得挺像回事。但 5 分钟前,同一个问题,它的回答是:

"冱忷倜昪邅轷欔靅..."(纯乱码)

5 分钟。同一个模型,同一套代码,从胡言乱语变成了说人话。

这 5 分钟里,模型经历了什么?我们把它拆开看。

一、模型就是一堆数字

先忘掉"人工智能"、"神经网络"这些词。把模型想象成一个黑盒子,里面装了 316 万个数字。

参数 #1 = 0.0023 参数 #2 = -0.1089 参数 #3 = 0.5471 ... 参数 #3,161,600 = 0.0091

就这些。没有魔法,没有神秘的 AI 大脑。就是 316 万个数字。

你给这个盒子输入"什么是注意力机制?",盒子用这些数字算一遍,吐出一个结果。

结果是什么,完全取决于这 316 万个数字是多少。

二、训练前:数字是随机的,输出是乱码

训练还没开始的时候,这 316 万个数字是随机初始化的。

就像你让 316 万个人随便站成一排,每人的姿势都是随机摆的。你问他们"什么是注意力机制?",他们互相之间毫无默契,七嘴八舌,最后输出的就是乱码。

问:什么是注意力机制? 答:冱忷倜昪邅轷... ← 纯乱码

这不是 bug。这是还没开始学习的状态。

三、训练中:一步一调,慢慢学

训练的过程,就是反复调整这 316 万个数字,让输出越来越接近正确答案。

具体来说,聚焦模型参数迭代更新环节,每次训练做 4 件事:

▪ 第 1 步:让模型猜一次(前向传播)

拿一条训练数据,比如:

输入:用:什么是注意力机制? 正确答案:助手:注意力机制通过计算查询和键的相关性分配权重

把输入喂给模型,让它用当前那 316 万个数字算一遍。

但模型不是"猜一个答案"。它做的事情更具体:对词表里的每一个词,预测它是不是下一个词。

我们的词表有约 1000 个词。模型输出 1000 个概率:

"注意" → 0.0001% "机制" → 0.0003% "蒸馏" → 0.0012% "的" → 0.0890% ...(1000 个词,每个都有一个概率)

这些概率加起来 = 100%。模型认为哪个词概率最高,就选哪个作为预测。是的,你的猜测是正确的,大模型本质上就是一个玩“文字接龙”游戏的概率预测机。

训练刚开始的时候,这 1000 个概率几乎是均匀分布的——模型觉得每个词都有可能,等于没有判断力。

▪ 第 2 步:看看猜错了多少(算 loss)

有了模型的预测概率,就能和正确答案对比。

正确答案的第一个字是"注"。模型给"注"预测的概率是多少?如果只有 0.1%,说明模型完全没猜对。如果是 90%,说明模型很确定。

Loss 就是用来量化"模型有多不确定正确答案"的指标。 我们用的是交叉熵损失(Cross-Entropy Loss),数学上:

loss = -log p

说明:p 为模型对真实类别输出的预测概率

  • 如果模型给正确答案的概率 = 100% → loss = -log(1) = 0(完美)
  • 如果概率 = 10% → loss = -log(0.1) = 2.3
  • 如果概率 = 0.1% → loss = -log(0.001) = 6.9

一个有趣的数字:训练刚开始时 loss ≈ 7.2。这不是偶然。因为模型随机初始化,1000 个词的概率差不多均匀分布,每个词约 1/1000 = 0.1%。所以初始 loss ≈ -log(1/1000) ≈ 6.9。实际测量 7.2,和理论值几乎一致。

这个数字说明:训练开始时,模型和瞎猜没区别。

▪ 第 3 步:找出哪些数字该背锅(反向传播)

这一步是关键。

模型怎么知道该调哪个数字?它给每个数字算一个"责任分数":

参数 #1 的责任: +0.0003 ← 你应该变大一点 参数 #2 的责任: -0.0015 ← 你应该变小一点 参数 #3 的责任: +0.0000 ← 你没影响,不用动 ... 参数 #3,161,600 的责任: +0.0007

316 万个数字,每个都算一个。这个计算过程叫"反向传播"——从输出往回推,看每个参数对错误贡献了多少。

直觉理解: 想象你在调音响。音乐太刺耳了(loss 高),你不知道该调哪个旋钮。反向传播就是告诉你:"低音旋钮往左转一点,高音旋钮往右转一点。"你有 316 万个旋钮,它能告诉你每一个该怎么转。

▪ 第 4 步:调一下数字(参数更新)

拿到责任分数后,每个数字往"正确方向"微调一小步:

参数 #1: 0.0023 → 0.0023 + 0.0003 = 0.0026 参数 #2: -0.1089 → -0.1089 - 0.0015 = -0.1104 ...

步子很小。 每个数字只动一点点(小数点后第 4 位级别)。调太多了会"过矫",从一个错误跳到另一个错误。

这个步子的大小叫学习率(learning rate)。我们设的是 0.001。

为什么是这个值?看一下不同学习率的效果:

学习率 1.0: loss 剧烈震荡,永远降不下来 ← 步子太大,来回跳 学习率 0.1: loss 下降快,但不稳定 ← 还是有震荡 学习率 0.001: loss 稳定下降,平滑收敛 ← 刚好 学习率 0.0001: loss 下降太慢,5000 步都不够 ← 步子太小

学习率不是一成不变的。我们的训练用了预热(warmup):前 100 步从 0 慢慢升到 0.001,让模型先用小步子探路,找到方向后再加速。之后用余弦退火(cosine decay)慢慢降低,让模型在接近最优解时步子越来越小,精确定位。

▪ 这 4 步循环,在模型身上发生了什么?

我们记录了训练过程中,每 500 步问模型同一个问题——"什么是注意力机制?"——看它怎么一步步从乱码变成人话:

step 0 (loss=7.2): 冱忷倜昪邅轷欔靅... step 500 (loss=0.8): 注意力...机...通...计算...←开始蹦出正确的词 step 1000 (loss=0.15): 注意力机制通过计算查询和键 ←句子通了,但没说完 step 2000 (loss=0.03): 注意力机制通过计算查询和键的相关性分配权重 ← 答对了 step 5000 (loss=0.005): 注意力机制通过计算查询和键的相关性分配权重,让模型动态关注最相关的部分。

你能看到一条清晰的轨迹:

  • 乱码 → 零星的正确词汇 → 半截正确的句子 → 完整的正确答案

这不是渐变。是从"完全不知道在说什么"到"突然蹦出正确的词"到"说完整句话"。就像小孩学说话——先是咿咿呀呀,然后突然蹦出"妈妈",然后是"妈妈抱",最后是完整的句子。

▪ 然后重复。5000 次。

一条数据调一遍不够。我们有几百条训练数据,每次取一批(batch size = 16),让模型对这 16 条数据都算一遍 loss,取平均,再反向传播调参数。这样做 5000 步:

每步: 取 16 条数据 → 模型猜 16 次 → 算平均 loss → 反向传播 → 调参数 5000 步 × 16 条 = 80,000 次训练样本

但我们的 batch 太大会撑爆内存。所以用了梯度累积(micro batch = 4):每次只算 4 条,攒 4 次的梯度再一起更新。效果等价于 batch size = 16,但内存只需要 4 条的量。

第 100 轮: loss = 2.13 → 还是胡说,但乱码变少了 第 500 轮: loss = 0.82 → 开始说中文了,但答不对 第 1000 轮: loss = 0.15 → 大部分答对了,偶尔串台 第 3000 轮: loss = 0.01 → 稳定答对 第 5000 轮: loss = 0.005 → 训练完成

316 万个数字,每轮被微调一次,5000 轮下来,每个数字被调了 5000 次。从随机变成"有意义"。

四、一个类比:316 万像素的屏幕

想象一块 316 万像素的屏幕。

训练前: 每个像素随机显示颜色——满屏雪花噪点。你什么也看不出来。

训练过程: 有个指挥官(训练算法)看一眼说"不对",然后告诉每个像素"你往红色偏一点"或"你往蓝色偏一点"。每个像素只调一点点。

5000 轮后: 雪花消失了。屏幕上出现了一幅清晰的画。

这幅画就是模型"学会"的知识。

每个像素本身没有意义。但 316 万个像素的排列组合,编码了"注意力机制是什么"、"RoPE 是什么"这些知识。

五、为什么需要 316 万个?不能少点吗?

可以少。但你少一个像素,画就缺一点。像素少了,画面就模糊。

10 万参数: 能记住 2-3 个字 316 万参数: 能记住 4-7 个字,勉强一句完整的话 80 亿参数 (Llama 3): 能写文章、写代码 1750 亿参数 (GPT-3): 能做推理、写小说

参数越多,能编码的知识越多。但每个参数做的事情是一样的——就是一个数字,被训练过程调到合适的值。

六、所以"训练"到底是什么?

一句话:训练就是通过反复试错,把 316 万个随机数字调整到合适的值,让模型从说乱码变成说人话。

没有魔法。就是:

1. 猜(前向传播)— 对词表里每个词预测概率

2. 看猜错了多少(交叉熵 loss)— 正确答案的概率越低,loss 越高

3. 找出谁该负责(反向传播)— 算每个参数对 loss 的贡献(梯度)

4. 调一下(参数更新)— 按学习率 0.001 微调

5. 重复 5000 次

这 4 步组成的循环,就是所有深度学习训练的底层逻辑。

不管是我们的 3M 模型还是 GPT-4,训练过程都是这 4 步。

区别只有两个:

  • GPT-4 有 1.8 万亿个参数(我们只有 316 万)
  • GPT-4 看了互联网上的全部文本(我们只看了几百条问答)

方法一样。规模不同。

七、你可能想问的

Q:反向传播是怎么算出每个参数的"责任"的?

用的微积分里的"链式法则"——从输出往回,一层一层算偏导数。但这个细节你不需要会手算。PyTorch 一行代码就帮你算了(loss.backward())。你只需要理解它的含义:算每个参数对错误的贡献。

Q:为什么步子要小?

想象你在山顶,要走到谷底(loss 最低的地方)。你看一眼坡度,往坡下的方向走一步。步子太大,可能直接跨过谷底冲到对面山上。步子小一点,虽然慢,但稳。

Q:316 万个参数怎么来的?不是越多越好吗?

参数量由模型配置决定:

词嵌入: 1000 个词 × 256 维 = 256,000 4 层 Transformer: 每层约 720,000 输出层: 与词嵌入共享,不额外算 总计: ≈ 3,161,600 (3.16M)

参数多 ≠ 效果好。你的数据量有限时,参数太多反而会过拟合——模型死记硬背,不会举一反三。3M 参数配几百条数据,刚好。

Q:反向传播算梯度的代价大吗?

反向传播的计算量和前向传播差不多。所以训练的实际耗时大约是推理的 2-3 倍(前向 + 反向 + 更新)。这也是为什么训练需要 GPU——不是前向传播慢,是反向传播也要跑一遍。

Q:316 万个数字,怎么存?

就是一个文件。checkpoints/best.pt 这个文件,里面就是训练好的 316 万个数字。约 12MB——一张照片的大小。

八、亲手验证

你可以在代码里看到这 4 步。打开 src/train.py,核心训练循环长这样:

for step in range(total_steps): # 第 1 步:前向传播——模型对每个词预测概率 logits, _ = model(x) #logits:[batch,seq_len,vocab_size] # 第 2 步:交叉熵损失——正确答案的概率有多低? loss = cross_entropy(logits, targets) # 第 3 步:反向传播——算 316 万个参数各自的梯度 loss.backward() # 第 4 步:按学习率更新参数 optimizer.step() optimizer.zero_grad() # 清空梯度,准备下一轮

logits 的形状是 [16, 128, 1000]:16 条数据 × 128 个位置 × 1000 个词的概率。cross_entropy 把它和正确答案对比,算出 loss。4 行代码,对应上面讲的 4 步。每一行在做的事,你现在都理解了。

这是「手撕 GPT」系列第 2 篇。上一篇:《CPU 就能训练 GPT?5 分钟跑一个能说中文的模型》。下一篇:《GPT 的核心代码只有 100 行》。

想亲手看到模型从乱码到说人话的过程?

运行 uv run python -m src.train,5 分钟后验收。

项目地址:https://github.com/helloworldtang/GPT_teacher-3.37M-cn

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 的数字化之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档