架构概述与设计哲学
1. 一个假设,而非一个功能
大多数推理引擎的目标是”更快”或”更省”。nuthatch 的出发点不同——它要验证一个假设:
MoE 模型每个 token 只激活少数专家。当显存装不下全部专家、必须留一部分在磁盘按需读时,用”历史使用”学出该常驻哪些专家(“learned pin”),能不能明显赢过朴素的 per-layer LRU、以及操作系统页缓存这个”免费基线”?
这个假设决定了整个项目的形状。它不是”再造一个 llama.cpp”——矩阵乘法、量化内核这些是 ggml 已经解决的 commodity。护城河在缓存策略这个算法上,以及围绕它的可复现研究。 引擎只是拿到”真实推理下的专家访问轨迹”的必要手段。
灵感来自 colibrì——一个把 744B 模型塞进 25GB 内存的单文件 C 引擎。但 colibrì 自造模型格式、缓存策略朴素(每层 LRU + 可选固定热专家)。nuthatch 反过来:原生吃 GGUF、把”缓存策略”做成真正的差异化。
目标是双份的:涨 credit(做一个有真实数据支撑、别人愿意看的东西)+ 练手艺(把推理引擎从 GGUF 解析到 MoE 前向到物理流式亲手过一遍)。
2. 三段式依赖链
代码刻意分成三段,依赖单向流动——这张图是理解整个仓库的钥匙:
① 读格式 ② 做研究 ③ 建引擎 ④ 接护城河
gguf/ GGUF 元数据 trace/ 路由轨迹格式 model/ model/
io/ 定位读 pread cache/ 策略 + 重放器 olmoe_model 加载 expert_reader 按需读专家
moe/ 专家张量布局 LRU/OS/learned attention 注意力 expert_slot_cache 有界槽缓存
moe MoE FFN streaming_model 显存受限加载
forward 整图前向 streaming_forward 两段式流式
generate 生成 + 导 trace
kv_cache KV 缓存
四段之间是”读格式 → 做研究 → 建引擎 → 把研究接进引擎”。
3. 一个反直觉的顺序:先做研究,再建引擎
最值得说的架构决定是:M2(研究)在 M3(引擎)之前完成。
“学习缓存是否更好”这个问题,本质只需要两样东西:一条路由轨迹(每 token 每层选了哪些专家),和一个能重放轨迹、数命中/缺失的缓存模拟器。这两样都不需要能跑的引擎。于是在写第一行前向代码之前,src/cache/learned_pin_policy_test.cc 里就已经有了第一个结果(合成轨迹上 learned 赢基线)。
这个顺序的价值:用最小成本先拿到研究信号,再决定值不值得投入把引擎写出来。 后来引擎写好了,真实轨迹替换掉合成轨迹,结论复现——研究的可信度上了一个台阶。
4. 一个 token 的生命周期
常驻路径(所有权重在内存,GreedyGenerateCached):
token ─► get_rows 取 embedding ─► 16 层 × [ 注意力(+残差) ─► MoE FFN(+残差) ] ─► output_norm ─► lm_head ─► argmax
流式路径(专家留盘,StreamingGenerate)把每层拆成两趟图,因为”选哪些专家”要等路由算完才知道:
每层:段A 注意力+路由 ─► 读回选中的专家 ─► host 装槽(miss 从盘读)─► 段B 在槽上算专家 FFN
两条路径的输出逐 token 完全一致——这是贯穿全项目的正确性纪律(见第 8、10 章的 parity 单测)。
5. 关键设计决策一览
后面各章会展开,这里先给全景(每一条都是”选了什么 + 否掉了什么”):
| 决策 | 选择 | 为什么 |
|---|---|---|
| I/O | pread+fadvise,非 mmap | 流式缓存要可控预取/驱逐;mmap 交给 OS 换页正是要打败的基线(第 3 章) |
| 模型格式 | GGUF | 事实标准、量化模型现成、ggml 原生、可对拍(第 3 章) |
| 语言 | C++ | ggml 是 C/C++,紧密算子调用跨 FFI 不划算;顺带 dogfood blade+vcpkg(第 2 章) |
| 内核 | ggml 走 vcpkg | 护城河在算法不在引擎,复用成熟内核 |
| 正确性 | 逐 token 对拍 llama.cpp | 每加一层锚一次(第 6 章) |
| 研究方法 | trace + 重放,先于物理执行 | 命中率结论不需要物理搬运专家(第 4、9 章) |
6. 怎么读这个仓库
跟着 Master Issue #1 的 PR 顺序读——仓库是按学习顺序长出来的,每个 PR 都有详细的”为什么这么做”,且带单测。本系列文档与 PR 顺序对齐:第 3 章对应 M1、第 4 章对应 M2、第 5-8 章对应 M3 与自包含、第 9-10 章对应护城河与物理流式。
下一章:代码结构、构建与 CI。