A from-scratch LLM stack

LLM from query
to GPU multiply.

8 层、~10K 行代码、零外部 LLM API、零外部 model 权重——预训练、SFT、Agent SFT、推理服务、KV cache、手写 BPE、CUDA + Triton。 RTX 5090 上 70 秒训出能调工具的小 agent。

这个 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 范围内的加法。但它在推理时正确生成 calc(1234 + 5678),把任意数字传给真 Python 工具。

这印证了 agent 的本质论点:模型学的是工具调用的格式契约("看到算术问题就调 calc"),不是工具的能力本身(计算)。这跟 ChatGPT 接 web_search、Cursor 接 grep+edit、Claude 接 computer use 是同一个本质。
Query类别输出
capital of France?KB lookup, in-dataParis.
23 plus 47?calc, in-data70.
1234 plus 5678?calc, OOD6912.✓ 关键泛化
Who wrote Hamlet?KB lookup, in-dataShakespeare.
chemical symbol of gold?KB lookup, in-dataAu.
capital of Mongolia?KB missnot found.✓ 诚实
How are you today?OOD conversationalhallucinate 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几乎没有