从一行 query,到 GPU 上的一次浮点乘法——一本可以跑起来的全栈教科书。
📖 在线阅读:https://fxp.github.io/LLM-from-query-to-result/
当你在 ChatGPT 里敲一句话,按下回车,屏幕上一个字一个字蹦出回答——这中间到底发生了什么?从浏览器里的字符,到 GPU 上的一次浮点乘法,要穿过多少层?
这个 repo 把整条链路切成 7 层,每一层都有独立的讲解和最小可运行代码。你可以单独跑任意一层,也可以把它们串起来看完整 trace。
这是真正的 from-scratch:
- ✅ 模型架构(L2,~330 行手写 GPT-2)
- ✅ 训练循环(L3,~140 行 AdamW + cosine schedule)
- ✅ 训练数据(L3,1.1 MB 莎士比亚纯文本)
- ✅ Tokenizer(L2
bpe.py,手写的 BPE,与 tiktoken 在中/日/emoji/标点上 bit-for-bit 等价) - ✅ KV cache(L2
GPT.step,自己实现) - ✅ 推理服务(L6,~140 行 FastAPI + SSE,零 transformers runtime 依赖)
- ✅ Instruct 微调(L4,~140 行 SFT 在自己 base model 上跑 28 秒,把 base 的"接龙莎翁"变成"答 'Paris.'")
- ✅ Chat 客户端(L8,纯 urllib HTTP)
- ✅ Web UI(L7,纯 HTML + fetch streaming)
- ✅ GPU kernels(L1,手写 CUDA matmul + Triton flash-attention,实测见 05_gpu/README.md)
唯一的"借":PyTorch 的 tensor / autograd(这是底座,不重写),可选下载 OpenAI 公开的 GPT-2 124M 权重(仅 fallback,默认走我们自训的 model)。
GPU 实测验证 (RTX 5090, 2026-05):
- L3 训练 1000 步:12.2 秒(CPU 6 分钟,60×加速)
- L4 SFT on 124M base:30 epochs / 33.9 秒,loss 1.6 → 0.0
- 推理:prefill 1.8 ms / decode 2.6 ms/token (124M, batch=1, fp32)
- L1 matmul 2048³:tiled 9.2 / cuBLAS 68.9 TFLOPS(cuBLAS 用 Tensor Core)
- L1 attention:Triton flash-attn 比 unfused PyTorch 快 8.5×
SFT 后用户 query: "What is the capital of France?"
最终产物: 浏览器里流式涌出 " Paris.<|endoftext|>" (greedy 模式)
这 3 个 token 全部由本仓自己训出来的 7M GPT 产出
完整流转:
- L3:莎士比亚 1.1MB → 我们的 BPE → 0.34M tokens → 我们的 GPT (n_layer=4, n_embd=128) → AdamW 1000 步 →
ckpt.pt - L4:base ckpt + 63 条手写 Q/A → SFT 50 epochs (28 秒) →
sft.pt(学会 instruction 格式 + 个别事实) - L6:load
sft.pt用我们的 GPT.step() 跑推理(KV cache 自己实现) - L8:把 query 包成
Q: ...\nA:POST 给 L6 - L7:浏览器 → FastAPI → SSE 流式返回每个 token
整条链路里,PyTorch 是唯一非自家的依赖(tensor 库 + autograd 是底座,不重写)。每个 token 经过的代码都在本 repo 里:算法、权重、tokenizer、KV cache、SSE 路由、UI 全部都是。
┌───────────────────────────────────────────────────────────────┐
│ 模型结构 │
│ L1 GPU 基础层 矩阵乘 / flash-attention 在 GPU 上怎么跑 │
│ 05_gpu/ (CUDA matmul + Triton flash-attention) │
├───────────────────────────────────────────────────────────────┤
│ L2 Transformer 从零实现 GPT-2:embed / MHA / FFN / LN │
│ 04_transformer/ (PyTorch, ~330 行 + 手写 BPE ~230 行) │
├───────────────────────────────────────────────────────────────┤
│ 训练 │
│ L3 预训练 prepare data → train loop → checkpoint │
│ 00_train/ (PyTorch + AdamW, ~140 行 ≈ 6 min CPU) │
├───────────────────────────────────────────────────────────────┤
│ L4 指令 SFT base ckpt + Q/A → instruction-tuned ckpt │
│ 00b_sft/ (~140 行 + 242 条手写 Q/A ≈ 28 sec) │
├───────────────────────────────────────────────────────────────┤
│ L5 Agent SFT instr ckpt + ReAct traces → tool-using ckpt │
│ 00c_agent_sft/ (~150 行 + 258 合成 traces ≈ 33s GPU) │
├───────────────────────────────────────────────────────────────┤
│ 推理与应用 │
│ L6 推理服务 tokenize / KV cache / SSE 流式输出 │
│ 03_model/ (FastAPI + 我们的 GPT.step,零 transformers) │
├───────────────────────────────────────────────────────────────┤
│ L7 App / Web UI 用户看到的聊天界面 + 后端 SSE 流式输出 │
│ 01_app/ (HTML + FastAPI) │
├───────────────────────────────────────────────────────────────┤
│ L8 Agent 循环 build prompt → 工具调度 → 注入 OBSERVATION │
│ 02_agent/ (chat 模式或 AGENT_MODE=1 的 ReAct loop) │
└───────────────────────────────────────────────────────────────┘
每一层独立且可跑:进入任意子目录,cat README.md 看讲解,按里面的命令就能运行。
浏览器 L7 后端 L8 客户端 L6 推理服务
─────── ───── ───────── ──────────
│ │ │ │
│ POST /chat │ │ │
│ ─────────────────▶│ │ │
│ │ run_agent(q) │ │
│ │ ────────────────▶│ │
│ │ │ POST /generate │
│ │ │ ────────────────▶│
│ │ │ │ tokenize + forward
│ │ │ │ ↓ L2 (GPT-2)
│ │ │ │ ↓ L1 (matmul)
│ │ │ data: {token} │
│ │ token event │ ◀────────────────│
│ SSE: token │ ◀────────────────│ ... │
│ ◀─────────────────│ │ ◀────────────────│
│ ... │ │ │
│ SSE: done │ │ │
│ ◀─────────────────│ │ │
pip install -r requirements.txt
# L1 的 Triton/CUDA 部分需要 NVIDIA GPU;没 GPU 可跳过。网络受限地区(如中国大陆)的运行清单:
git clonegithub.com 直连超时——用 mirror 前缀:git clone https://gh-proxy.com/https://github.com/fxp/LLM-from-query-to-result.gitpip install默认走 PyPI,CN 区一般 ok(或自己配 aliyun/tsinghua mirror)。- HF Hub (
GPT.from_pretrained("gpt2")) 直连不通时自动 probe + fallback 到https://hf-mirror.com,并设HF_HUB_DISABLE_XET=1绕开慢的 Xet CDN。控制台会打印:手动指定 endpoint:HF Hub direct unreachable; using mirror: https://hf-mirror.com (set HF_HUB_DISABLE_XET=1, HF_HUB_DOWNLOAD_TIMEOUT=60)export HF_ENDPOINT=...(已设的会跳过 probe)。- BPE vocab (
encoder.json+vocab.bpe) 来自openaipublic.blob.core.windows.net——CN 区可直连。- Tiny Shakespeare 已 bundle 在
00_train/data/input.txt,完全无需下载。
# Step 1: 训自己的 base model(M1 CPU ~6 min,1000 步,loss 10.8 → ~4.5)
cd 00_train && python prepare.py && python train.py
# Step 2: SFT 让它能听懂问答(M1 CPU ~28 sec,50 epochs,loss 9 → 1.7)
cd ../00b_sft && python train.py
# Step 3: 用 SFT'd ckpt 起 L6 服务
MODEL_PATH=$(pwd)/out/sft.pt python ../03_model/server.py
# Step 4: 另开终端,起 L7 web app + 浏览器
cd ../01_app && uvicorn backend.main:app --reload
# 浏览器打开 http://localhost:8000,问 "What is the capital of France?"
# → " Paris." ← 这 2 个 token 你自己端到端造出来的# 需要 GPU。先把 base 升级到 124M,再做两轮 SFT
cd 00b_sft && python train_from_gpt2.py # ~34s on RTX 4080S,instruct SFT
cd ../00c_agent_sft && python build_data.py && python train.py # ~34s,ReAct agent SFT
# 然后用 agent.pt 起 L6
MODEL_PATH=$(pwd)/out/agent.pt python ../03_model/server.py
# 起 L7 时设 AGENT_MODE=1,启用 ReAct 循环
cd ../01_app && AGENT_MODE=1 uvicorn backend.main:app --reload
# 浏览器问 "What is 1234 plus 5678?"
# 💭 I need to compute 1234 + 5678.
# 🔧 calc(1234 + 5678)
# ↳ 6912
# → 6912. ← calc tool 真算的,model 不会自己算大数如果不想等训练,可以加载 OpenAI 公开的 GPT-2 124M 权重(首次会下载 ~500MB):
# 终端 1:L6(不设 MODEL_PATH 就走 GPT.from_pretrained("gpt2"))
cd 03_model && python server.py
# 终端 2:L7 web app
cd 01_app && uvicorn backend.main:app --reloadcd 00_train && python prepare.py && python train.py # 训 base
cd 00_train && python sample.py "ROMEO:" # 采样 base
cd 00b_sft && python train.py # SFT
# L8 / client.py 依赖 L6 先起 03_model/server.py
cd 02_agent && python agent.py "What is the capital of France?"
cd 03_model && python server.py
cd 04_transformer && python inference.py "Hello, I am"
cd 04_transformer && python bpe.py # 验证手写 BPE
cd 05_gpu && python benchmark.py # 需要 NVIDIA GPU| 形式 | 链接 | 长度 / 风格 |
|---|---|---|
| 🌐 Web 落地页 | web/index.html |
单页 HTML,hero + demo + 架构图 |
| 📑 实验报告(Web) | web/report.html |
学术风 HTML,TOC + 表格 + 引用 |
| 📊 实验报告(Markdown) | reports/EXPERIMENT_REPORT.md |
~6500 字,方法/结果/讨论/可复现 |
| 📚 单篇浓缩 Blog | blog/article.md |
~5500 字,故事化叙述 |
| 📖 11 篇分章 Blog | blog/ |
~30K 字,每层一篇 |
| 🧪 冷启动实测日志 | reports/ |
三次独立 cold-start 的原始 stdout |
📖 分章 blog 索引(11 篇):
| # | 文章 | 主题 |
|---|---|---|
| 00 | 序章 | 全栈视角 + 设计原则 |
| 01 | L3:从莎士比亚训出一个 GPT | 预训练循环、loss 为什么从 10.8 降到 4.5 |
| 02 | L4:24 秒变 instruct | SFT、loss masking、知识 vs 格式 |
| 03 | L7:浏览器里那一个个蹦出来的字 | SSE + ~80 行前端 |
| 04 | L8:Chat 客户端的最小本质 | 删掉所有不必要的抽象 |
| 05 | L6:自己写 KV cache 的推理服务 | prefill vs decode、HF mirror fallback |
| 06 | L4a:300 行手写 GPT-2 | embed / MHA / FFN / LN / KV cache |
| 07 | L4b:手写 BPE,bit-for-bit ≡ tiktoken | byte 映射、regex 预切词、merge 规则 |
| 08 | L1:一次矩阵乘在 GPU 上到底怎么跑 | naive vs tiled vs cuBLAS、Triton flash-attn |
| 09 | 端到端 trace:从一句 query 到一次浮点乘法 | 9 层串起来 |
| 10 | L5:教一个 124M 模型用工具 | ReAct agent SFT;calc + lookup 工具;33 秒训 |
每篇 1500-3000 字,5-10 分钟。源码都开放,跑一遍看实测数字。
想看 model 是怎么"诞生"的:L3 — 6 分钟在 CPU 上把 loss 从 10.8 (random) 降到 ~4.5,看到 forward → loss → backward → optimizer 闭环。
想看 base model 怎么变成 instruct model:L4 — 28 秒 SFT 把 "续写莎翁" 的 base 变成 "答 'Paris.'" 的 instruct 模型。
产品/应用开发者:从 L7、L8 开始,看清楚一条 chat 请求从浏览器一路下到推理服务的每个环节。
做 infra / 推理优化:重点看 L6(KV cache 的实际实现,~140 行无外部依赖)和 L1(kernel 层做优化的地方)。
想理解模型本身:L2 是核心——~330 行看懂 transformer,加 ~150 行手写 BPE。L3 / L4 / L6 都用同一个 GPT 类。
全都想懂:按 L3 → L4 → L1..L1 顺序读下来,examples/trace.md 里有一条从 query 一路到 GPU 指令的完整 trace。
| 目录 | 层 | 语言 | 运行依赖 |
|---|---|---|---|
00_train/ |
训练 base | Python | torch, regex |
00b_sft/ |
SFT 微调 | Python + JSON | torch |
01_app/ |
Web App | Python + HTML/JS | FastAPI |
02_agent/ |
Chat 客户端 | Python | 标准库 (urllib) |
03_model/ |
Model 服务 | Python | FastAPI, torch |
04_transformer/ |
Transformer + BPE | Python | torch, regex |
05_gpu/ |
GPU kernel | CUDA / Triton | nvcc, triton, CUDA GPU |
examples/ |
end-to-end trace | Markdown | — |
- 每层核心代码 < 300 行:超过就说明讲多了,砍掉。
- 不引入陌生抽象:能用标准库就用标准库,不造框架。第三方依赖只剩 PyTorch(不重写)+ FastAPI(写 web server 没必要重新造)+ regex(BPE 需要 unicode pattern)。
- 零外部 LLM API、零外部 LLM 库 runtime:L3 训 base、L4 SFT、L6 推理(含 KV cache)、L2 BPE 全部本地代码。L2 的
from_pretrained("gpt2")是唯一可选用 transformers 的地方(仅下载 OpenAI 权重时),跳过它全栈零 transformers/tiktoken 依赖。 - "看得见"优先于"快":L3 print loss 下降,L4 print SFT 前后对比,L6 print KV cache 长度,L2 print 每层激活 shape,L2 BPE 自测对齐 tiktoken,L1 有 roofline benchmark——看得见才算讲清楚了。
- 一个贯穿例子:base 模型用
ROMEO:续写莎翁,SFT 后用What is the capital of France?测 instruction following。
MIT