架构概述与设计哲学

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


This site uses Just the Docs, a documentation theme for Jekyll.