这个 demo
浏览器输入 What is 1234 plus 5678?,按下 Enter——下面是真实流式输出(1234+5678 不在 SFT 训练数据里,但 model 学会了通过 calc 工具扩展能力):
👤 What is 1234 plus 5678?
💭 I need to compute 1234 + 5678.
🔧 calc(1234 + 5678)
↳ 6912 // Python eval, not model
🤖 6912.
124M 模型自己绝对算不对 4 位数加法(chat-SFT 版本会答 "13")。带 calc 工具就行。这就是 agent 的本质——model 能力 = 知识 + 工具扩展。
八层架构
每一层 < 300 行核心代码,独立可跑。整个栈在 RTX 5090 上从零跑通用 ~70 秒。
L1
GPU 基础层 ·
05_gpu/手写 CUDA naive matmul / tiled matmul(演示分块),Triton fused flash-attention(vs PyTorch unfused 8.4× 加速)
~165 lines
L2
Transformer 架构 + BPE ·
04_transformer/330 行手写 GPT-2(embed/MHA/FFN/LN/KV cache)+ 230 行手写 BPE(与 tiktoken bit-for-bit 等价,7/7 测试集 100% 通过)
~560 lines
L3
预训练 ·
00_train/从随机权重训出 7M GPT。在 1.1MB 莎士比亚语料上 1000 步训练,loss 从 10.815 (= ln(50257)) 降到 4.55
12 sec
L4
指令 SFT ·
00b_sft/242 条手写 Q/A 教 base model 答问。Path A 用我们自训 7M base,Path B 用 OpenAI gpt2-124M(解锁世界知识)
28 sec
L5
Agent SFT ·
00c_agent_sft/258 条 ReAct traces 教 model 调用 calc/lookup 工具。Loss masking 让 OBSERVATION prefix 学但内容不学
33 sec
L6
推理服务 ·
03_model/FastAPI + 自实现 KV cache。Prefill 1.8 ms / decode 2.6 ms (124M, 5090, batch=1)。零 transformers runtime
~140 lines
L7
App / Web UI ·
01_app/FastAPI + SSE 流式输出 + ~80 行原生 HTML/JS 前端。浏览器一个 token 一个 token 收到 thought / action / observation
~130 lines
L8
Agent 循环 ·
02_agent/默认是 chat completion 客户端。AGENT_MODE=1 启用 ReAct loop:generate-stop-parse-execute-inject 循环
~200 lines
关键数字(实测)
RTX 5090, 三次独立 cold-start 验证。完整原始日志见 reports/。
70 sec
L3 + L4 + L5 训练总耗时(不含 124M 权重下载)
10.815
L3 step 0 loss = ln(50257) 理论值,验证权重初始化
7/7 ✓
手写 BPE bit-for-bit 等价 tiktoken(含中文/日文/emoji)
< 1e-6
手写 KV cache vs full forward 数值差(浮点精度)
8.4×
Triton flash-attention vs PyTorch unfused 加速比
8 / 10
Agent E2E 测试答对率(greedy decoding,1234+5678→6912 ✓)
最重要的 demo:1234 + 5678 → 6912
这条记录是整个项目最重要的实测之一。
Model 在 SFT 训练里只见过 1-99 范围内的加法。但它在推理时正确生成
这印证了 agent 的本质论点:模型学的是工具调用的格式契约("看到算术问题就调 calc"),不是工具的能力本身(计算)。这跟 ChatGPT 接 web_search、Cursor 接 grep+edit、Claude 接 computer use 是同一个本质。
calc(1234 + 5678),把任意数字传给真 Python 工具。这印证了 agent 的本质论点:模型学的是工具调用的格式契约("看到算术问题就调 calc"),不是工具的能力本身(计算)。这跟 ChatGPT 接 web_search、Cursor 接 grep+edit、Claude 接 computer use 是同一个本质。
| Query | 类别 | 输出 | |
|---|---|---|---|
| capital of France? | KB lookup, in-data | Paris. | ✓ |
| 23 plus 47? | calc, in-data | 70. | ✓ |
| 1234 plus 5678? | calc, OOD | 6912. | ✓ 关键泛化 |
| Who wrote Hamlet? | KB lookup, in-data | Shakespeare. | ✓ |
| chemical symbol of gold? | KB lookup, in-data | Au. | ✓ |
| capital of Mongolia? | KB miss | not found. | ✓ 诚实 |
| How are you today? | OOD conversational | hallucinate lookup | ✗ 已知失败 |
从零启动
完整 cold-start 命令(CN 区,已测三次):
# 1. Clone (CN 区直连 github 不通,走 gh-proxy)
git clone https://gh-proxy.com/https://github.com/fxp/LLM-from-query-to-result.git
cd LLM-from-query-to-result
# 2. 配 pip mirror + 安装
mkdir -p ~/.pip
echo -e "[global]\nindex-url = https://mirrors.aliyun.com/pypi/simple/\ntrusted-host = mirrors.aliyun.com" > ~/.pip/pip.conf
pip install -r requirements.txt
# 3. 训 base + SFT + agent SFT (~70 秒在 5090)
cd 00_train && python prepare.py && python train.py
cd ../00b_sft && python train.py && python train_from_gpt2.py # 后者首次会下 124M 权重 (HF mirror 自动 fallback)
cd ../00c_agent_sft && python build_data.py && python train.py
# 4. 起服务(agent.pt 是带工具的 124M 模型)
MODEL_PATH=$(pwd)/out/agent.pt python ../03_model/server.py &
AGENT_MODE=1 uvicorn 01_app.backend.main:app --port 8000 &
# 5. 浏览器打开 http://localhost:8000
# 输入 "What is 1234 plus 5678?" → 一个个 token 蹦出 "6912."
深度阅读
| 文章 | 主题 | |
|---|---|---|
| 📚 | 单篇浓缩版 | 整个项目的故事,4000 字 |
| 📊 | 实验报告(HTML) · Markdown | 正式格式:方法、结果、讨论、可复现性 |
| 📖 | 11 篇分章 blog | 每层一篇,~30K 字总量 |
| 🧪 | 原始实验日志 | 三次独立 cold-start 的完整 stdout |
| 💻 | 完整源码 | ~10K 行 Python + ~165 行 CUDA |
差异化贡献
业界已有许多优秀"从零写 GPT"教程(karpathy/nanoGPT、minbpe 等)。本项目的差异化:
| 主题 | 大多数教程 | 本项目 |
|---|---|---|
| GPT 架构 | ✓ | ✓ |
| 预训练循环 | ✓ | ✓ |
| BPE tokenizer | 一些(minbpe) | ✓ 与 tiktoken 验证 bit-for-bit 等价 |
| SFT instruction tuning | 很少 | ✓ |
| Agent SFT + ReAct loop | 几乎没有 | ✓ |
| 推理服务(KV cache + SSE) | 很少 | ✓ |
| Web 前端 | 几乎没有 | ✓ |
| GPU kernel 内幕 | 一些 | ✓ Triton + CUDA 对比 |
| 从浏览器到 matmul 的端到端 trace | 几乎没有 | ✓ |