[控场AI]
· 7 分钟阅读· 3,991 字

MIT作者打造2MB模型:跑进CPU L3缓存,单核3405 tok/s

MIT作者打造2MB模型:跑进CPU L3缓存,单核3405 tok/s

开发者把1.9MB模型塞进CPU的L3缓存,60美元CPU单核跑出3405 tok/s。

文章介绍了开发者 mdnazib963 的一个反常规推理实验:不与 GPU 比算力,而是把模型的全部权重设计得能驻留 CPU 的 L3 缓存。1.91M 参数、int8 量化后仅 1.97 MB,恰好占 Ryzen 5 3400G 4 MB L3 的一半,于是 decode 退化为缓存流式读取。通过 evict_read 对照实验,预热状态 3405 tok/s,缓存被污染后降至 1681 tok/s,惩罚 2.03×,证明缓存确实在起作用。质量上,C 运行时与 PyTorch 参考实现困惑度差异仅 0.082%,贪心 token 匹配 99.41%。整套流水线从数据清洗到 C 运行时全部开源可复现,作者坦承输出很弱、目前仅支持 Windows x64,并邀请他人用 JSON 结果复现或证伪。

大模型推理的叙事几乎被 GPU 垄断,但 Reddit 上一篇来自开发者 mdnazib963 的帖子给出了另一种思路:不跟 GPU 比算力,而是让模型小到能住进 CPU 的 L3 缓存里。结果是一颗 60 美元 CPU 的单核跑出 3405 tok/s,模型只有 1.9 MB。

rss source: I made a language model that lives in your CPU's L3 cache

一个刻意无聊的起点

作者在年初给自己定了一个规格:单核 50 tok/s。这个数字故意选得很“无聊”——llama.cpp 在笔记本上就能做到,不算什么挑战。但他顺着这条线挖了九个月,最终做出来的东西完全不同:一个从零训练的 1.9 MB 模型,在 60 美元 CPU 的单核上跑到 3405 tok/s。

真正有意思的不是这个数字,而是一句话解释:他把 CPU 缓存从“实现细节”重新定义为“设计目标”。

一次前向传播就是一次权重读取

推理的账其实很朴素。每一次前向传播都要把权重完整读一遍,这个读取过程就是成本本身。

作者模型的参数是 1.91M,int8 量化后权重占 1.97 MB。而他用的 Ryzen 5 3400G 有 4 MB L3 缓存。1.97 除以 4 等于 49.8%——权重刚好占了 L3 的一半。

如果权重能塞进缓存,decode 就退化成缓存流式读取:约 6.7 GB/s 的顺序读,由 L3 直接服务。如果塞不进,每次前向都要走 DRAM,就要跟带宽、延迟和资源争抢缠斗。

大多数推理优化在卷计算,而 cache-fit 的思路反其道而行:用消除内存问题的方式,让计算不再是瓶颈。

模型结构不是碰巧这么小,而是照着缓存反向设计出来的:4 个 gated short-conv 块加 2 个 grouped-query attention 块(kv=1,所以 KV cache 每层只有一个向量,而不是 seq_len×d),词表 4096 让输出头不至于反客为主,int8 导出让字节数等于参数量。

L3 缓存与内存层次:现代 CPU 有多级缓存,L1 最小最快(通常几十 KB,延迟约 4-5 周期),L2 居中(几百 KB 到几 MB),L3 为末级缓存,容量通常 4-32 MB,被所有核心共享。缓存的物理位置紧贴计算单元,访问延迟只有 DRAM 的约百分之一,带宽也高出数倍。所谓"cache-fit",就是指模型的全部权重常驻 L3,每次前向传播读取权重时命中缓存,从而绕开 DRAM 的带宽瓶颈与访问延迟。L3 一旦被其他数据(如另一个进程的随机读取)挤出,权重就得回落到 DRAM,速度随之骤降——这正是文中 evict_read 模式的原理。理解这一层次结构,是理解"把缓存当设计目标"的前提。

int8 量化与 QAT-lite:int8 量化把原本 16 位或 32 位的浮点权重压缩成 8 位整数存储,字节数直接对齐参数量(1.91M 参数约 1.97 MB),从而让权重更易塞进缓存。代价是精度损失,需通过量化感知训练(QAT)在训练阶段模拟量化误差来补偿。文中的"QAT-lite"指一种轻量方案:不一定全程模拟,而是在训练尾段或导出前引入量化约束,使 C 运行时与 PyTorch 参考实现的困惑度差异控制在 0.082%(门槛 ≤1%)。per-input 激活量化 则是量化推理中的另一环:每次输入的激活值单独求缩放因子,减少动态范围差异带来的误差,代价是额外计算,作者用 AVX2 指令把它摊薄到可接受程度。

用一个“对照组”证明缓存确实在起作用

一个没有对照的数字算不上测量。所以作者的 benchmark 里有一个 evict_read 模式:在模型 decode 的同时,用另一个进程拿随机读取猛击 L3。

状态tok/s
预热(权重驻留 L3)3405
驱逐(L3 被污染)1681
惩罚2.03×

同一个二进制、同一个模型、同一个位置,唯一变量是缓存是否驻留。2.03× 这个差距是测出来的,不是争出来的。作者还提供了一个 stream 命令做反向探针(128 MB 顺序扫描),用来显示你机器原始的 DRAM 带宽,方便自己对比这两个数字。

Grouped-Query Attention(GQA)与 KV cache:标准多头注意力中,KV cache 的规模是 seq_len × d × 层数,序列越长占用越大,是推理显存的主要消耗项之一。GQA 让多个查询头共享一组键值头,把 KV 头数从多头压到极少。文中模型把 kv 设为 1,即每层只有一个键值向量,KV cache 被压到与序列长度几乎无关,这既减少了内存占用,也降低了每步解码的读取量。对 cache-fit 目标而言,注意力层的 KV cache 若过大,会挤占本就不宽的 L3 空间,因此 kv=1 是结构上配合缓存的必要设计,而非单纯的速度优化。

速度没有拿质量来换

速度宣称很廉价,质量持平的宣称才贵。作者的 C 运行时必须跟导出它的 PyTorch 参考实现对齐,而且是硬性门槛:

  • 困惑度:C 端 26.8407 对 PyTorch 26.8188,差异 0.082%,门槛是 ≤1%
  • 贪心 token 匹配:99.41%,门槛是不匹配率 ≤1%
  • 确定性测试和 AddressSanitizer 套件:全部干净
  • 所有门槛结果都作为 JSON 记录签进仓库 phase0/phase5_results.json

int8 的实现上,per-input 激活量化在所有行之间摊薄,符号扩展点积用了 vpmovsxbw 加 vpmaddwd 和 4 个累加器,注意力用 AVX2,exp 走七次多项式。光是把 float 内核换成 int 内核这一步,就从 1494 tok/s 提到 3696 tok/s。

整条流水线可复现

作者把从数据到运行的每一步都摊开了:

python data/clean.py && python data/generate.py && python data/pack.py
python train.py --train data/train.bin --val data/val.bin --out run --d 144 ...
python phase0_export.py --ckpt run/best.pt --out phase0/model.i8
phase0\build_p0.bat
phase0\phase0.exe phase0\model_p3.i8 bench data\val.bin 15.0 65

从 Gutenberg 语料清洗、合成任务流生成、BPE、训练、QAT-lite int8 导出到 C 运行时的完整链路,语料构建约 80 秒。benchmark 只打印一行 JSON——你要么复现出他的数字,要么复现不出。

它做不到什么

这部分作者写得很直白,也值得照搬:

  • 输出很弱。1.9M 参数是一个设计研究,不是聊天机器人。它是用来验证 cache-fit 论点的基准载体,作者没打算假装它还有别的用途。
  • 目前只有 Windows x64。MSVC 构建能用,Linux/macOS 的 Makefile 还在 issue #2 里。如果帖子有热度,这会成为第一优先级。
  • 一个模型、一类机器。数字来自 Ryzen 5 3400G(4 MB L3)。L3 更大,余量就更大;仓库里的 info 命令会告诉你这台 CPU 的 fit_half_l3 是多少。

符号扩展点积与 AVX2 实现细节:int8 推理在 CPU 上要高效,关键在于用 SIMD 指令一次处理多个整数。文中提到的 vpmovsxbw 把 8 位整数符号扩展到 16 位,vpmaddwd 再做乘加并累加到 32 位,配合 4 个累加器可隐藏指令延迟、提升流水线吞吐。注意力部分用 AVX2 向量化,exp 用七次多项式近似以避免调用昂贵的库函数。这套内核是"float 换 int"带来 1494→3696 tok/s 提升的直接原因:整数运算在 CPU 上比浮点更快、更省功耗,也更贴合缓存流式读取的节奏。要复现数字,这些底层实现是不可跳过的一环。

为什么这件事值得公开

ML 推理里真正有意思的主张通常围绕 GPU 展开。但内存层次结构的论证是可以推广的:如果你的权重能放进计算单元够得着的地方,整个问题的形状就会变。

把模型尺寸对齐缓存,是验证这件事最便宜的地方——不需要 H100,一颗 60 美元的老 CPU 就够了。作者的整套代码、内核和测量全部 MIT 开源,地址在 GitHub 的 mdnazib963/CoreFlow,权重发布在 Hugging Face 的 nazib61/coreflow-1.9m。

他还留了一个请求:如果你在自己的硬件上复现(或复现失败),带着 JSON 输出开个 issue。硬件多样性才是重点。这一点其实比 3405 tok/s 更接近工程精神的本质——数字的意义不在于炫耀,而在于它能不能在别人的机器上站得住。

#CPU推理#L3缓存#小语言模型#int8量化#AVX2#llama.cpp#缓存驻留优化#端侧推理
分享:

相关推荐