跳到正文
HANDS-ON · BUILD ONE SMALL MODEL

别只看架构图,亲手让一个 GPT 的 Loss 降下来

先用 NumPy 把每个张量从 Token ID 追到 Logits,再沿链式法则走回来;随后换到结构相同的 PyTorch 版,只用 --device 改变执行位置。一个项目同时回答“梯度怎么算”和“怎样真的放到单 GPU”。

字符 token 进入透明的微型 Transformer 装置,依次经过 Embedding、因果注意力、残差、MLP 与输出分布;下方 Loss 曲线下降并封存 checkpoint,右侧连接 CPU、CUDA GPU 与 MPS 设备
直觉总览:模型结构、训练目标和 checkpoint 合同保持不变,设备只决定张量在哪里算。插画不表达精确张量形状;下方 SVG 和可运行代码负责精确机制。 查看原图 ↗
01 · 先得到一次成功

两分钟跑通,再带着真实输出读代码

项目不下载模型,也不联网取语料。NumPy 版专门展示手写反向;PyTorch 版保留同样的单 block 数据流,并把设备、保存和加载补成真实工程路径。`corpus.txt` 会重复 24 次,种子固定为 42。

PROJECT TREE
examples/tiny-gpt/
├── corpus.txt          # 固定四句小语料
├── requirements.txt    # NumPy 手写版依赖
├── tiny_gpt.py         # 手写前向、反向、Adam、采样
├── test_tiny_gpt.py    # 解析梯度 vs 有限差分
├── requirements-pytorch.txt
├── tiny_gpt_torch.py   # CPU / CUDA / MPS、保存与加载
├── test_tiny_gpt_torch.py # checkpoint / 断点随机流回归
└── README.md           # 两条路径与失败排查
RUN · CPU

可复制命令

在项目根目录执行。第一次创建隔离环境,之后只需运行最后一行。

TERMINAL
cd examples/tiny-gpt
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python tiny_gpt.py --steps 240 --seed 42 --prompt 模型 --new-tokens 8
VERIFIED · 2026-07-17

本站实跑结果

训练前后使用同一组均匀窗口评估,避免拿两个随机 batch 的 Loss 假装提升。不同 NumPy/BLAS 版本末位可能有细小差异。

ACTUAL OUTPUT · NUMPY
eval_before 4.0076
step 060 | train_loss 0.4141
step 120 | train_loss 0.0796
step 180 | train_loss 0.0414
step 240 | train_loss 0.0186
eval_after 0.0118
loss_delta 4.0076 -> 0.0118
sample 模型从下一个字开始学
saved tiny-gpt.npz + tiny-gpt.tokenizer.json + tiny-gpt.manifest.json
PYTORCH · CPU SMOKE VERIFIED

同一设备脚本先在 CPU 验证

这不是伪代码。本站使用 PyTorch 2.8.0 在 Apple Silicon CPU 实跑训练,并立即重新加载 checkpoint;不同版本末位可能略有变化。

TERMINAL
python -m pip install -r requirements-pytorch.txt
python tiny_gpt_torch.py \
  --device cpu --steps 12 --seed 42 --new-tokens 8 \
  --save /tmp/tiny-gpt-torch-smoke.pt
ACTUAL OUTPUT · 2026-07-17
device cpu | seed 42 | parameters 7,951
eval_before 4.0080
step 001 | train_loss 4.0078
step 006 | train_loss 3.9729
step 012 | train_loss 3.8534
eval_after 3.7994
saved /tmp/tiny-gpt-torch-smoke.pt + ...tokenizer.json
先别误会

这段续写主要说明模型记住了极小语料的局部规律,不代表它拥有通用语言能力。教学项目的成功标准是“链路正确且可解释”,不是输出像线上大模型。

02 · 前向传播

一个 Token 不会“进入模型一下”,它会连续换六种身份

ID 只是地址;Embedding 把地址查成向量;Attention 让当前位置读取左侧上下文;MLP 在每个位置独立改写特征;LM Head 再把隐藏向量投影成整个词表的分数。

Tiny GPT 前向架构:Token ID 经过字符和位置 Embedding、因果自注意力、残差、MLP、第二次残差与 LM Head 得到词表 Logits;下方列出各组参数学习的作用。
图中 B 是 batch,T 是序列长度,D 是隐藏维度,V 是词表大小。示例使用 B=8、T=24、D=24,只有一个注意力头和一个 block。 查看原图 ↗
tok[idx] + pos[:T]

同一个“模”字在不同位置共享字符向量,再叠加不同位置向量,所以模型同时知道“它是谁”和“它在哪”。

scores = Q @ K.T / √D

Q 是当前位置想找什么,K 是每个历史位置能被怎样匹配。右上角被因果 Mask 设为负无穷,模型不能偷看答案。

attention @ V

Softmax 把匹配分数变成权重,再对 V 做加权求和。它得到的是“从历史位置取回的信息”。

x + attention; x + mlp

两次残差都保留旧信息并加上新修正,让深层模型更容易传递信号。这个教学版省略 LayerNorm,生产 Transformer 通常会使用。

03 · 训练与生成

训练是一圈“看答案再改参数”,生成是一圈“没有答案继续猜”

训练样本 `x` 是一段字符,`y` 是同一段整体向左移动一位。模型在所有位置同时做下一字符预测。交叉熵只取正确字符的概率;概率越小,惩罚越大。

Tiny GPT 训练与采样闭环:batch 经前向得到 logits 和交叉熵,反向传播产生梯度,Adam 更新参数并反馈给下一次前向;训练后提示词经前向、Softmax 和采样追加下一个 Token,循环生成。
蓝色是训练前向张量,红色是从 Loss 到参数的梯度与更新,绿色是推理采样循环。采样阶段不会调用 backward 或 Adam。 查看原图 ↗
手算 · 错一位的监督

文本“模型从”怎样产生三组输入和答案?

输入是“模、型、从”,目标是“型、从、下一个字符”。位置 0 只能看“模”并猜“型”;位置 1 能看“模型”并猜“从”。一个长度 T 的 batch 会同时贡献 B×T 个下一字符训练点。

VOCAB语料字符数

字符级词表,方便完全看懂;真实模型通常使用 BPE 类子词。

CONTEXT24 字符

超过长度时,采样只保留最近 24 个字符。

MODELD=24

单头、单 block、MLP 隐层 48,刻意保持小。

SEED42

初始化与 batch 固定;采样用 seed+1 的独立随机流。

04 · 手写反向传播

反向不是把公式倒放,而是给每条分叉把责任加回来

前向里一个张量可能走两条路。例如 `x1` 一路直接进入第二次残差,一路进入 MLP。反向经过这两条路时,必须把两份梯度相加;这就是计算图中“多条下游贡献汇合”。

LM Head 与交叉熵

Softmax 概率减去 one-hot 正确答案,得到 Logits 梯度;它同时产生 Head 权重梯度和回到隐藏状态的梯度。

MLP 与残差

先过 W2,再乘 `1 - tanh²(z)`,再过 W1;同时保留残差直通的那份梯度。两路在 `dx1` 相加。

Attention

从输出投影回到加权和,再分别得到 Attention 权重梯度与 V 梯度;Softmax 的雅可比被写成逐行向量公式,之后回到 Q、K、V 和四个矩阵。

Embedding 查表的反向

同一个字符可能在 batch 中出现多次,不能直接赋值梯度。`np.add.at` 会把所有出现位置的梯度累加到同一行 Embedding。

数值与公式双重检查

代码对梯度做 [-1,1] 裁剪,并在每一步显式检查 Loss、概率和梯度是否有限。test_tiny_gpt.py 还对 Embedding、位置向量、Q/K/V、Attention 输出、MLP 与 LM Head 的十个代表性元素做中心有限差分;2026-07-17 本地测试全部通过。有限差分只是抽查,不替代逐式审阅,但能抓住遗漏残差、转置或缩放等常见错误。

05 · 真实单 GPU 路径

现在不是“可以迁移”,而是同一个脚本直接选择 CUDA

tiny_gpt_torch.py 只有一条训练代码。--device cpu 已完成本站冒烟测试;在装有 NVIDIA GPU 与匹配 PyTorch 的机器上,把参数改成 cuda,模型、batch、前向、Loss 和反向就会留在同一张 GPU。auto 则按 CUDA → MPS → CPU 选择。PyTorch 官方同样要求 Module 与 Tensor 显式移动到目标设备,跨设备计算不会自动替你修正。R07R08

RUN · NVIDIA CUDA

先确认可见,再训练和保存

CUDA 路径与 CPU 冒烟测试调用同一个文件;第一条检查必须输出 True,训练第一行必须显示 device cuda,才算真的用上 NVIDIA GPU。

TERMINAL
python -c "import torch; print(torch.__version__, torch.cuda.is_available())"
python tiny_gpt_torch.py \
  --device cuda --steps 240 --seed 42 --prompt 模型 \
  --save tiny-gpt-torch.pt

保持不变

字符/子词编码、`x/y` 错一位、因果 Mask、Embedding→Attention→MLP→Head、交叉熵和逐 Token 采样。

交给框架

nn.Module 管参数,loss.backward() 构图求梯度,torch.optim.AdamW 管优化器状态;它与 NumPy 手写版互相对照。

只搬一次

model 与 batch 都移到所选设备,位置 ID 又在 token_ids.device 上直接创建;不会在每层来回搬 CPU/GPU。

别期待这个例子更快

模型小到无法填满 GPU,kernel 启动和数据传输开销可能超过计算。GPU 优势要在更大矩阵或更大 batch 才明显。

CHECKPOINT V3 · LOAD / RESUME

保存的不只是一份权重

checkpoint 同时带结构、state_dict、优化器、词表、累计步数、seed、下一批训练的 RNG 状态、语料 SHA-256 和运行时版本。加载前逐项检查精确 schema、shape、dtype、有限值、参数/字节上限与语料指纹;steps=0 只生成,steps>0 从记录的随机流继续训练。

LOAD ONLY
python tiny_gpt_torch.py --device cpu --steps 0 \
  --load /tmp/tiny-gpt-torch-smoke.pt --new-tokens 8
RESUME 20 STEPS
python tiny_gpt_torch.py --device auto --steps 20 \
  --load /tmp/tiny-gpt-torch-smoke.pt \
  --save /tmp/tiny-gpt-torch-resumed.pt
安全与证据边界

本站已真实验证 CPU 的训练、保存、加载、断点续训,并在 Apple Silicon 上验证 auto 选择 MPS。当前测试机没有 NVIDIA GPU,因此这里不伪造 CUDA 数字;CUDA 使用相同代码路径,并在设备不可用时给出明确失败提示。依赖与运行时均要求 PyTorch ≥2.6,避开低版本 weights_only=True 的 CVE-2025-32434;之后仍严格校验模型/AdamW 张量和资源上限,不为旧框架回退到不受限 pickle。陌生 checkpoint 依然不可信。R09R10R11

06 · 失败排查

先按症状定位,不要同时乱改五个超参

找不到 NumPy / PyTorch

确认激活了正确环境。手写版安装 requirements.txt;设备版安装 requirements-pytorch.txt,不要混淆两个环境。

CUDA 被判定不可用

先看 torch.cuda.is_available()。False 通常意味着 PyTorch 构建、NVIDIA 驱动或 GPU 可见性不匹配;先用 CPU 验证,不要把 auto 回退误认为 CUDA 成功。

Tensor 不在同一设备

如果自行修改了脚本,新张量要在 token_ids.device 创建,model 与 batch 也必须同设备。不要在每层之间反复调用 cpu/cuda。

CUDA 显存不足

恢复 batch=8、block=24、dim=24,关闭同卡其他进程。默认模型只有 7,951 个参数,本身只需要很少显存。

Prompt 出现未知字符

字符 Tokenizer 只认识语料字符。错误会列出具体未知字符;先用“模型”,或修改语料后重新训练与保存词表。

Loss 不降 / 非有限

恢复 seed=42、lr=0.006、batch=8、block=24。一次只改变一个量,并保留初始 Loss 做对照。

Checkpoint 加载失败

NumPy 的 .npz 与 PyTorch 的 .pt 不能混用。v3 checkpoint 会拒绝 schema、shape、dtype、NaN/Inf、资源上限、RNG、词表或语料哈希不匹配;不要为“能打开”而改用 weights_only=False

输出仍像随机字符

先判断固定评估 Loss 是否从约 4 降到更低。极小语料只能验证代码链路,不能训练通用中文模型。

RESEARCH LEDGER

一手来源与证据边界

优先使用论文、官方文档、官方模型卡和代码仓库。页面中的数字只代表来源所述设置,不自动外推到其他模型与数据。

R01
Attention Is All You NeedVaswani et al. · 2017

缩放点积注意力、前馈网络、残差连接和 Transformer 主结构。

R02
Language Models are Unsupervised Multitask LearnersOpenAI · 2019

GPT-2 的自回归语言模型目标和 decoder-only 扩展路径。

R03
minGPTAndrej Karpathy · official GitHub repository

把 GPT 训练主干压缩到可阅读代码的经典教育实现。

R04
nanoGPTAndrej Karpathy · official GitHub repository

从教学实现走向多 GPU、混合精度和真实语料训练的下一步。

R05
Adam: A Method for Stochastic OptimizationKingma & Ba · 2014

示例中一阶矩、二阶矩与偏差修正的来源。

R06
NumPy GeneratorNumPy official documentation

固定随机数生成器、批次采样和可复现实验。

R07
CUDA semanticsPyTorch official documentation · updated 2026-06-01

设备无关代码、CUDA 可用性与张量所在设备的官方说明。

R08
MPS backendPyTorch official documentation · updated 2026-05-11

Apple Silicon 上把 Tensor 与 Module 移到 MPS 设备的官方路径。

R09
Saving and Loading ModelsPyTorch official tutorial

state_dict、optimizer state 与跨设备加载 checkpoint 的推荐做法。

R10
Serialization semanticsPyTorch 2.13 documentation · updated 2026-05-08

state_dict 推荐路径、2.6 起 weights_only 默认行为,以及受限反序列化仍不防拒绝服务和所有内存破坏风险。

R11
CVE-2025-32434: torch.load with weights_only=True leads to RCEPyTorch official security advisory · 2025

PyTorch 2.5.1 及以下的 weights_only 反序列化漏洞与 2.6.0 修复下限。