这是一篇沉浸式长文,建议在专属页面阅读 → 进入完整版
这是 Field Note 系列的第九篇。姐妹篇是 《一行 JS 的一生 · QuickJS 源码详解》(讲一个引擎全栈)和 《V8 是怎么把 JS 跑快的》(讲多层 JIT)。本篇换一种语言模型——LLM 推理——但还是同一个手法:主线一段 prompt,对着真源码看它一站站怎么变形。
主线:5 个 token,走过 28 个站
prompt = "你好,llama"
│
▼
┌─────────── Phase 0 · 引子 ─────────┐
│ C01 三眼看 "你好" │
│ C02 prefill vs decode │
├─────────── Phase I · 入口 ─────────┤
│ C03 Tokenizer · BPE │
│ C04 Embedding + 内在维度 │
│ C05 Batch / request lifecycle │
├─────────── Phase II · 心脏 ────────┤
│ C06 RMSNorm + RoPE (math) │
│ C07 QKV projection │
│ C08 KV cache + attention sinks ⭐ │
│ C09 Attention + online softmax │
├─────────── Phase III · 变体 ───────┤
│ C10 GQA / MQA │
│ C11 MLA (DeepSeek) ⭐ │
│ C12 MoE 路由 ⭐ │
├─────────── Phase IV · 出口 ────────┤
│ C13 LM head + vocab-parallel │
│ C14 Sampling (min-p/DRY/mirostat) │
│ C15 EOS + stop matching │
├─────────── Phase V · 全景 ─────────┤
│ C16 一次完整 forward 的 stack │
│ C17 vLLM vs llama.cpp + CUDA Graph │
├─────────── Phase VI · 生产 ────────┤
│ C18 量化 zoo (GGUF/AWQ/GPTQ/SQ) ⭐ │
│ C19 投机解码 (EAGLE) + 前缀缓存 ⭐ │
├─────────── Phase VII · 规模化 ─────┤
│ C20 Continuous batching ⭐ │
│ C21 Chunked prefill │
│ C22 TP / PP / EP 多 GPU ⭐ │
├─────────── Phase VIII · 协议 ──────┤
│ C23 Streaming + Chat template │
│ C24 Constrained decoding ⭐ │
├─────────── Phase IX · 前沿 ────────┤
│ C25 Multimodal (vision encoder) ⭐ │
│ C26 Reasoning · o1/R1/test-time ⭐ │
│ C27 Hardware · A100/H100/B200 │
└─────────── Coda · 工具箱 ──────────┘
C28 怎么自己 trace 一次推理
这一版深挖了什么
- 逐行真源码:28 个站全部对应
llama.cpp/vLLM仓库里一个具体文件、一个具体函数。llm_tokenizer_bpe::tokenize·llama_kv_cache_unified::find_slot·ggml_flash_attn_ext·llama_sampler_chain_apply·block_q4_K·PrefixCachingBlockAllocator.allocate_with_prefix·Scheduler.schedule·RowParallelLinear·llama_grammar_apply_impl·clip_image_encode· 一连串都在文里逐行读过。 - 一条主线 prompt:
"你好,llama"——前 12 章用 Llama-3-8B 跑,到 MLA 和 MoE 那两章换成 DeepSeek-V3,到 TP/PP 那一章上 Llama-3-405B,到 multimodal 那章 prompt 变成 “这张图里有什么” + 一张猫的图片。 - prefill ≠ decode 的二相世界:一次推理其实是两个完全不同的负载——prefill 是矩阵×矩阵,吃算力;decode 是矩阵×向量,吃带宽。KV cache 把它们粘起来;prefix caching 把”共享开头”再省一刀;speculative decoding 把”串行 decode”压成”并行 prefill”;continuous batching 让它们在同一个 batch 里混跑;chunked prefill 解决”长 prompt 拖死 decode”的不公平;reasoning 模型把 decode 数量 × 50,所有这些优化的边际价值翻倍。
- KV cache 的真实重量:Llama-3-8B 在 8K 上下文下 KV cache 约 1 GB;70B 服务在 8×H100 装下 170 个并发用户,KV cache idle 时占内存就是 “OpenAI 毛利 60% 不是 90%” 的原因。Attention sinks 论文 解释为什么去掉开头 4 个 token 模型立刻崩,KV cache 压缩(H2O/SnapKV/PyramidKV)各家在选择性遗忘上的取舍。
- FlashAttention 的 online softmax 数学:维护 running max 和 running denominator,配合那个
exp(m_old - m_new)的修正系数——这是 FA 不物化 S 矩阵的关键。FA v1 → v2 → v3 三代演化跟 A100 / H100 硬件代际深度耦合。 - RoPE 的完整复数推导:把 Q/K 的每对维度看作复平面上的点,旋转 mθ 角度,Q·K^T = Re(q·k*·e^(i(m-n)θ))——绝对位置自然消失,只剩相对位置。
- MLA 的吸收数学:把
K = c_kv · W_uk代进 attention,Q · Kᵀ = (Q · W_ukᵀ) · c_kvᵀ——W_ukᵀ可以在模型加载时一次性融进W_q,运行时只算一次 matmul,KV cache 只存 latent。一步一步推导。 - MoE 家族大谱:Mixtral 8x7B / DBRX / Phi-MoE / DeepSeek-V2 / DeepSeek-V3 / Llama-4 Maverick / Llama-4 Behemoth 同表对比。从 softmax 到 sigmoid,从无共享专家到有,从粗到细。
- Continuous batching:vLLM 真正的杀招,把吞吐量从 1× 涨到 5-20×,因为 GPU 终于不再 “带薪喝咖啡”。配合 CUDA Graph + Triton kernel 又是一截。
- 多 GPU 三种切法:TP(同层切矩阵,all-reduce)· PP(不同层放不同卡,气泡问题)· EP(MoE 专家分布,all-to-all)。Llama-3-405B 部署配方算到每张卡 GB 级。
- Constrained decoding:GBNF 语法引擎把 JSON Schema 编译成 FSM,每个 token 都用 trie 索引验证——OpenAI structured output 不是魔法,是工程。
- 量化的真实位段:
block_q4_K256 weights → 144 bytes 的精细切法,Q4_K_M 的 “M” = mixed (attn.wv / ffn.w2 / LM head 用 Q6_K),fp8 e4m3 vs e5m2 的用法分歧。GGUF / AWQ / GPTQ / SmoothQuant / HQQ 五种方案的取舍。 - Speculative 三代演化:vanilla → Medusa → EAGLE → spec+prefix sharing,逐代命中率和实现复杂度。
- 生产引擎的真实优化栈叠加:static batching → +continuous batching (5×) → +PagedAttention (12×) → +chunked prefill (16×) → +prefix caching + speculative (30×)。同模型同硬件,wall time 13.3s → 3.5s。
- Multimodal · pixels as tokens:vision encoder 把图片切 patch → CLIP-ViT → projector → 当 token 喂进 LLM。一张高清图等价 ~1500 个文字 token,要 prefill。Late fusion / cross-attention / native multimodal 三种融合路线的取舍。
- Reasoning models · o1/R1 改写了推理 economics:thinking 阶段 20000 个内部 token,decode 数量 × 50。“thinking 不流式” “parallel thinking” “thinking compression” 等很新的优化方向。
- 硬件代际:A100 → H100 → H200 → B200,每两年关键指标翻倍。WGMMA / TMA / fp8 / fp4 这些硬件特性如何重塑推理路径。GB200 NVL72 单机柜 72 张卡的影响。
- llama.cpp vs vLLM:同一件事,两套写法。CUDA Graph capture / Triton kernel / cuBLAS 三层 kernel 栈。各自牺牲了什么。
读完你会对”按下回车之后那 200 毫秒里到底发生了什么”形成一个非常具体的 mental model,能看懂 llama.cpp 的 llama_decode 日志、能读懂 vLLM 的 PagedAttention 论文、能解释 FlashAttention 的 online softmax 数学、能算清楚 405B 服务一节点能承载多少用户、能在面试或 review 里说清楚 “这个模型为什么推理慢”。
“一个 LLM 看起来在’思考’,但它真正做的事情,是 5 个 token 在 4096 维的隐空间里走 32 层楼梯,每层楼梯上都铺着同一张 KV 桌子。”
Comments
0 comments