把 SSD 当显存用:本地跑前沿 MoE 的真实账本

2026-11-16T07:15:00+08:00 | 5分钟阅读 | 更新于 2026-11-16T07:15:00+08:00

@
把 SSD 当显存用:本地跑前沿 MoE 的真实账本

colibri 的 README 第一句写得很狂:tiny engine, immense model。启动 32 秒,常驻 9.9GB,跑的是 GLM-5.2,744B 参数的 MoE,int4 量化,GPU 可有可无。项目今年 7 月 1 日建仓,现在 5 万多 star。

我先给判断:这些数字是真的,但它测的是「能不能跑起来」,不是「能不能拿来干活」。这两件事之间隔着一段内存带宽的距离,还有一类被多数 benchmark 忽略的负载形状。

路由稀疏性给了这条路一个入口

MoE 的体积分布很歪。路由专家占了参数量的绝大部分,但每个 token 只挑其中几个。DeepSeek-V4-Flash 的路由专家合计 137GiB,四张 24GB 的 4090D 在装完 dense 权重和 KV cache 之后,只放得下一半左右,剩下的必须在主机内存里待着。

反过来看,attention、router、shared expert 这些每个 token 都要用的部分,体积小得多,永远留在显存里不会动。colibri 的三层划分就是照着这个不对称来的:热专家进显存(CUDA 或 Vulkan),温的留在内存,冷的从 NVMe 现读。

所以「小机器跑大模型」不是一个魔法,是一次针对权重分布的重排:把常驻部分压缩到最小,把稀疏部分挪到慢速存储上按需取。

上传还是就地算,是一道除法题

真正决定速度的不是显卡型号,是你怎么在两种代价之间选。

一个 DeepSeek-V4-Flash 的专家在 MXFP4 下占 13.4MB。通过 PCIe 4.0 在空闲总线上传一次要 0.53ms,CPU 算这个专家的一个 token 要 0.18ms。除一下就知道:一次前向里如果有三个以上 token 命中同一个专家,传过去用 GPU 算才划算;只命中一两个,在 CPU 上就地算更快。

llama.cpp 默认的 32 token 上传阈值,ik_llama.cpp 里按 total_experts / active_experts 放大的那个系数,源头都是这道除法。工程上的很多「魔数」,其实是某个硬件参数除出来的结果。

再往下一层是 NVMe。SSD 顺序读有三到七 GB/s,随机读是另一个数量级,而 MoE 的路由天生是随机访问。colibri 这类项目真正的工程量不在「能不能读」,在「怎么把随机读变成大块连续读」:专家按访问模式重排、O_DIRECT 绕过页缓存、双缓冲预取把下一层的读和这一层的算叠起来。这些手段能不能生效,取决于模型文件在磁盘上的物理布局,跟你买什么卡关系不大。

缓存策略已经走到第四代

  • 整层放置。-ot "exps=CPU" 把整层专家丢给 CPU。简单,prefill 快,但 decode 掉到 47.1ms 一个 token,比专家缓存方案的 31.5ms 慢了五成。
  • 专家级 LRU。把命中的专家留在显存,未命中再传。llama.cpp 今年十月合入的 --moe-cache-mib 走这条路,但只服务 32 token 以内的前向。
  • 按用量学习热集。colibri 把每轮对话的路由热力记到 .coli_usage,用得越久越快。它把自己类比成 JIT 编译器:不编译整个程序,只编译跑热的那几条路径。这个类比站得住,因为路由确实有可测量的结构。
  • 跨层共享池加显存借用。Ostinato 把 prefill 预留但 decode 期间闲置的显存借给冷专家,四张卡上塞了 544 个专家槽,解码一步能省 0.4 到 1.4ms。

这条演进线的方向很清楚:从「按层分」到「按专家分」,再到「按你实际怎么用来分」。静态配置正在输给运行时学习。

Agent 把前面所有假设都打破了

前面提到的缓存都有一个隐含前提:decode 是主要的时间开销。agent 不是这么用的。

一次编码会话会发 42 到 100 个请求,每次工具返回之后只 prefill 中位数 249 个 token。这种短 prefill 触及的专家又散又多,上传一次摊不薄,于是现有引擎干脆让缓存靠边站,冷专家全交给 CPU 算。实测下来,每次工具调用之后,用户要等 2.4 到 2.8 秒才看到第一个字。

Ostinato 这篇论文的观察值得记住:短 prefill 的路由虽然分散,但 84% 落在同一个稳定的、装得进显存的专家集合上。于是它让显存里的那批专家参与每一次前向,只把剩下不到一个的冷路由交给 CPU,配合固定形状的 CUDA graph 和零槽位,把 agent 会话的总时间压掉 22% 到 55%。

这件事对做产品的人比对做推理引擎的人更重要:如果你优化的指标是 decode 的 tokens/s,而用户感受到的是每次工具返回后的等待,那你优化得越狠,体感可能越糟。

自己上手该测什么

  • 每次工具调用后的首字延迟。这是 agent 用户唯一真正感知的数字。
  • prefill 和 decode 分开测。这两个经常反向,混在一起的平均值没有意义。
  • KV cache 单独算账。MLA 架构的 KV 很小,几 GB 量级,可能专家权重才是唯一装不下的东西;换成非 MLA 的模型,长上下文会把你刚省下来的显存全部吃回去。
  • 磁盘侧的三个数:随机读 IOPS、page fault 次数、SSD 累计写入量。缓存替换策略会反复写盘,笔记本上的盘经不起折腾。
  • 别忘 --load-mode none。默认的 mmap 会让 CPU 专家变成可分页内存,实测 prefill 慢 2.8 倍、decode 慢 1.15 倍,日志里其实已经提示了。

几点冷水

「8GB 内存跑 744B」的前提是最小模型 22GB 磁盘再加网络,能跑通和能干活是两回事,上下文长度和速度都要打折。磁盘流式的真实吞吐高度依赖 SSD 型号和文件布局,别人跑出来的数大概率不是你的机器。

还有一个容易被忽略的落差:这部分工程大厂内部早就趟过一遍了,KTransformers 那套东西发了 SOSP 论文,DALI 这类工作在校准抢占和缓存替换上做得更细。开源社区现在补的是工程细节和硬件适配,不是发现了新大陆。

最后

显存不够这件事,短期不会靠买卡解决。真正的分水岭是把「模型必须完整驻留」这个假设扔掉,按访问频率把权重摊到 HBM、内存、NVMe 三层上。这套思路在操作系统和数据库里叫缓存层级,在推理里才刚被认真对待。

谁的调度器把这个层级做对,谁就能在同样的硬件上多跑一档模型。而判断一个项目是不是真做对了,不看它贴出来的 tokens/s,看它在 agent 这种碎请求负载下的首字延迟。


参考来源:

Me

Cut out summary from your post content here.

The remaining content of your post.