跳到正文
LLM 推理

交叉索引

五个层次

这门课按依赖关系排序,而不是按主题。每一层的存在,都是为了解决上一层制造出来的问题;真要动手写一个引擎,顺序也是这样。

1

模型本身

4 章 · 826 行

把权重变成 token:分词器、Transformer 前向传播、采样。任何优化出现之前,这个循环就已经在了。

  1. S01生成循环自回归解码推理引擎就是一个把模型自己的输出再喂回去的 while 循环。这门课余下的全部内容,都是在优化这个循环。137
  2. S02分词从字节到 token id模型从来看不到文本。BPE 分词器把字节压缩进一个固定词表,你报出的每个延迟数字都以 token 计,而不是以字符计。232
  3. S03Transformer 前向传播RMSNorm、RoPE、GQA、SwiGLU现代 decoder block 只有七个操作。用 NumPy 老老实实写一遍,之后所有 kernel 优化就都有了对照的正确答案。251
  4. S04采样把 logits 变成一个 token采样器是引擎里最便宜的部分,却是用户感受最强烈的部分。temperature、top-k、top-p、min-p 和各种惩罚,全都是在对 logits 动手术。206

现在你有了一个正确、但每生成一个 token 都要重算全部历史的引擎。

2

显存与 KV 缓存

4 章 · 1003 行

几乎所有推理优化本质上都是显存优化:缓存 KV、给它分页、共享、压缩。

  1. S05KV 缓存decode 为什么是显存带宽瓶颈没有缓存时,生成第 N 个 token 的代价是 O(N²);有了缓存则是 O(N)。瓶颈也随之从算力转移到显存带宽。205
  2. S06PagedAttention给 KV 缓存做虚拟内存为每个请求分配连续 KV 缓冲区,会因碎片浪费掉 60–80% 的 KV 缓存显存。把缓存分页成固定大小的 block,几乎能全部收回。283
  3. S07前缀缓存同一段 prompt 绝不算第二遍共享的 system prompt、few-shot 示例和多轮对话意味着:大多数 prefill token 之前已经算过了。给 block 做哈希,然后复用。263
  4. S08量化让每个权重和每条 KV 少占几个 bitdecode 速度取决于每个 token 搬运的字节数,位宽减半,延迟也几乎减半。剩下要决定的,是让误差落在哪里。252

引擎已经很快,稀缺的资源换成了显存,不再是算力。

3

批处理与调度

4 章 · 1059 行

单个请求会浪费一整张 GPU。连续批处理、调度器、抢占和分块 prefill 让它保持满载。

  1. S09连续批处理迭代级调度静态批处理让所有请求都得等最慢的那一个。改成在每次前向之间进出请求,吞吐能提升数倍。244
  2. S10调度器队列、预算与抢占每一步调度器都要决定哪些请求能跑。公平性、延迟 SLO、显存不足后的恢复策略,都在这里。442
  3. S11分块 Prefill别让长 prompt 卡住所有 decode一次 32k token 的 prefill 会把排在它后面的所有 decode 堵上一秒。把 prefill 切成小块,混进 decode 批次里。190
  4. S12FlashAttention在线 softmax,不落 N² 矩阵注意力根本不需要把分数矩阵物化出来。分块加上滚动的最大值与求和,就把 O(N²) 的显存开销变成了 O(N)。183

一张 GPU 现在被大量并发请求填满,限制它的是 kernel 本身。

4

解码加速

4 章 · 863 行

打破「一次前向只出一个 token」的规则,并约束模型被允许说出什么。

  1. S13算子融合与 CUDA Graph干掉 kernel 启动开销小 batch 时 GPU 是在等 CPU。融合算子、回放已捕获的 graph,能省掉每个 token 数百次的 kernel 启动。185
  2. S14投机解码一次前向吐出不止一个 token廉价的草稿模型先猜 k 个 token,大模型一次前向就能验证全部 k 个。拒绝采样保证输出分布可证明地完全一致。188
  3. S15结构化输出受语法约束的解码合法 JSON 不是一个 prompt 问题。把 schema 编译成状态机,然后把语法不允许的 logit 全部屏蔽掉。338
  4. S16专家混合(MoE)稀疏计算,稠密显存一个 MoE 层对每个 token 只激活 256 个专家中的 8 个。算力降了,显存没降,路由则变成了负载均衡问题。152

单机的余量已经被榨干。剩下的问题,一台机器装不下了。

5

分布式服务

4 章 · 1186 行

把模型切分到多张 GPU,把 prefill 与 decode 拆到不同机器,再在前面加一层 API。

  1. S17张量并行与流水线并行一个模型,多张 GPU先按列切、再按行切每个 matmul,每个子层只需要一次 all-reduce。流水线并行则是用延迟换容量。216
  2. S18Prefill/Decode 分离部署两种负载,两组机器prefill 受算力约束,decode 受带宽约束。挤在同一张卡上,两个 SLO 都达不到;那就把它们拆开,再把 KV cache 传过去。186
  3. S19服务层OpenAI 兼容的流式 API用户看到的是 TTFT 和 token 间延迟,不是 FLOPs。API 层负责流式推送 SSE、处理取消,并导出你真正拿来调优的指标。363
  4. S20完整的引擎把所有零件接起来把前十九章的零件装成一个引擎:调度器、分页缓存、前缀复用、投机解码、流式服务器,最后跑一遍基准测试。421

现在你有了一个引擎。这个领域余下的工作,都是在细化上面这几步。

6

收官之作

2 章 · 4513 行

拿一个真实的 2944 行 C 引擎,逐模块用 Python 重写一遍。全课程只有这一章的参考实现,是别人正在用的生产代码。

  1. S21用 Python 重写 picoLM一个真实的引擎,逐模块拆解picoLM 用 2944 行 C,在一块 10 美元、256 MB 内存的板子上跑起了 11 亿参数的模型。逐模块把它移植到 NumPy,这门课的每一章都能在里面找到,连它有意省掉的那两章也算在内。495
  2. S22用 TypeScript 写 mini-picoLM同一个引擎,就跑在这个标签页里把引擎第三次重建一遍:一个 ArrayBuffer、一个 arena、十八个步骤,做出一个不需要服务端、不需要 WASM、零依赖就能在浏览器里生成文本的引擎。JavaScript 里难的是内存,不是数学。4018

现在你能从头到尾读懂一个生产引擎了,因为你写过一个版本:它对的地方逐字节吻合,它错的地方不跟着错。

全部概念,以及它们在哪里被构建

只想找某一个具体机制、而不是读完一整章时,从这里进入。