卡住本地大模型的不是算力,是你的硬盘

小岛AI 2026 / 07 / 29

14.6 秒。

这是一台 64GB 的 M1 Max 吐出一个 token 需要的时间。换算过来是 0.0687 token/s(tokens per second,每秒生成的 token 数,中文里一个 token 大致就是一个汉字上下),一分钟大概 4.1 个字。你问它一句「土星最大的三颗卫星是什么」,楼下便利店都能来回一趟了,它可能还在酝酿第一句话。

跑的是 Kimi K3,2.8 万亿参数,权重加起来 1.56TB。就是月之暗面前几天刚开源、官方 API 文档里写着 100 万上下文的那个 K3。

这个项目叫 Deltafin,今天上午刷到它挂在 Hacker News 首页,124 分 85 条评论。评论区第一反应几乎都一样,这有什么用。有人算了笔账,按这个速度生成 100 万 token 要 3.2 年,而同样的量走 API 大概 3 美元。

说真的,这个吐槽我完全接受,它在事实层面挑不出毛病。

但我把作者贴出来的性能剖面拉开看了一遍,然后有点坐不住了。因为那 14.6 秒里,真正在做矩阵乘法的时间,只有 1 秒。

作者很实在,把 M1 Max 上一个 token 的时间都拆开摆着了,大概是这么个分布。

等常驻主干从硬盘读出来,大约 5 秒。读这一层被选中的 16 个专家,大约 4.3 秒。把主干搬进显存再解量化,大约 3 秒。93 层的注意力和归一化,大约 2 秒。真正的专家矩阵乘法,大约 1 秒。

好家伙。算数学占 7%,剩下 93% 全在搬运字节。

M1 Max 上生成一个 token 的 14.6 秒耗时构成,真正做矩阵乘法的只有 1 秒

这个比例摆在这儿,我第一反应是,那这台机器到底缺的是什么。它不缺 GPU,M1 Max 那颗 32 核 GPU 在这个任务里基本是闲着的,一秒钟干完活等着后面。它缺的是内存装不下、于是所有东西都得反复从 NVMe 里捞出来的那个窘境。

要理解这个窘境怎么来的,得先说清楚 MoE 这三个字母。

MoE 是 Mixture of Experts,混合专家。你可以粗暴地理解成,这个模型不是一整块,而是被切成了一大堆小块,每一块叫一个专家。K3 有 82432 个这样的路由专家。每生成一个 token,每一层有个叫路由器的小模块出来指路,从这一层的 896 个专家里挑 16 个,只有这 16 个会被真正读进来算。剩下那 880 个,这一步压根没被碰。

所以 2.8T 这个数字,从来就不等于「每一步都要动用 2.8T」。它更像一个图书馆的藏书量,而你这次借阅只翻了其中十几本。

Deltafin 干的事,就是死死抠住这个性质。1.45TB 的专家权重全部躺在硬盘上,用 mmap 映射着,路由器点到谁就读谁,一层 16 个专家,92 层,每个 token 实际读进来 25.8GB。这部分反而不是最要命的。

最要命的是另外那块,作者管它叫 resident spine,常驻主干。注意力、共享专家、隐层投影、词嵌入这些每个 token 都必然要过一遍的部分,加起来 114GB,量化到 int8 之后压到 53GB。

53GB,塞在一台 64GB 的机器上。

你想想看这个局面。系统本身要吃掉几个 G,PyTorch 和 Python 进程要吃掉几个 G,专家读进来的那 25.8GB 还指望 page cache(页缓存,操作系统把最近读过的文件内容留在内存里,下次读就不用碰硬盘)能兜住一部分。

53GB 的主干和 25.8GB 的专家读,在 64GB 里抢地盘,谁都不肯让。

结果就是每生成一个 token,那 53GB 里的大半都得重新从 NVMe 里读一遍。作者算过,这个访问模式在他那台机器上能跑到 7GB/s 左右,53GB 除以 7GB/s,7.5 秒。14.6 秒里有 7.5 秒,就这么没了。

作者自己在文档里写了一句我觉得挺有分量的话,解码现在是被常驻主干的磁盘带宽卡住的,除非有更多内存能让它常驻,或者主干本身变小,否则这 7.5 秒躲不掉。

这是一句非常工程的话。它不谈参数量,不谈跑分,它说的是这个东西现在卡在哪个物理器件上。

我一直觉得,判断一个技术项目是不是真做过东西,看的就是这种句子。愿意公开说「我现在卡在硬盘带宽」的人,是真的把 profiler 挂上去跑过的。反过来那种只报一个漂亮数字、剖面绝口不提的,你大概能猜到里面有多少水分。

Deltafin 里有个社区贡献的测试,一个叫 Maurice Brown 的人在 NVIDIA DGX Spark 上跑了同一套东西,128GB 统一内存,PR #2 里贴了四组配置的对比,四组产出的文本完全一致。

bf16 主干 + CPU,总共 865 秒。 bf16 主干 + CUDA,858 秒。 int8 主干 + CPU,649 秒。 int8 主干 + CUDA,221 秒。

第一眼看这四行,很容易得出一个平平无奇的结论,量化有用,GPU 有用。但比较骚的事在于,int8 相对 bf16 只把主干体积砍了一半,端到端却快了差不多 4 倍,不是 2 倍。

多出来的那 2 倍是从哪冒出来的。

答案藏在 preload wait 这一列,也就是等待预加载的时间。bf16 + CUDA 那组,计算只用了 160 秒,preload wait 却高达 676 秒。到了 int8 + CUDA 那组,计算变成 184 秒,preload wait 塌成了 18 秒。

计算时间几乎没变,甚至还涨了一点。真正被消灭的是等待。

DGX Spark 四组配置的耗时分解,int8 砍掉的是那根红色的等待条

原因不复杂,107GB 的 bf16 主干在一台 128GB 的机器上,几乎把所有内存都占满了,留给专家读的 page cache 只剩一点渣。于是每次读专家都得老老实实碰硬盘,预加载线程永远跟不上,计算线程就在那儿干等。换成 53GB 的 int8 主干之后,一下子空出了几十 G 的缓冲区,专家读大部分命中缓存,等待时间直接坍缩。

所以量化在这里改变的不是乘法有多快,是整台机器的 I/O 处于什么状态。

作者自己也点了这一句,他说这提醒我们量化能改变 I/O 的整个格局,而不只是算术开销。

我看到这儿愣了一下。因为我们平时聊量化,讨论的坐标几乎永远是精度掉多少、速度快多少倍、困惑度涨了几个点。很少有人把它当成一个内存预算问题来谈。可一旦模型大到装不进机器,量化的第一价值就不再是省算力,而是省出那几十 G 的呼吸空间,让整个流水线从「一直在等」变成「基本不等」。

这是个临界现象。在模型能装下的时候,量化就是省点显存加点速度,线性的、可预测的。在模型装不下的时候,量化可能一脚把你从一个制度踢进另一个制度,收益是非线性的。

作者在文档里列了一串改进的实测数据,如果你不知道瓶颈在 I/O,看着会觉得很杂。知道之后,会发现它们指向同一个方向。

打包的 MPS int8 输出头,稳态解码快了 17.3%,预填充快了 23.1%,整体吞吐涨了 26.8%。同时列了一个数字,这部分权重的存储从 4.7GB 降到 1.17GB。

看到没,加速比和体积缩减比是咬在一起的。省下来的 3.5GB 不是省了几次乘法,是又给 page cache 腾了 3.5GB。

n-gram 推测解码,一次前向能吐两个 token,重复文本会成比例地快。这个更直接,前向次数减半,意味着那 53GB 的重读次数也减半。而且作者强调它是无损的,接受的草稿 token 能精确复现参考序列,被拒绝的草稿会把模型状态逐位还原回去。

推测性快照从 3.56 毫秒降到 0.001 毫秒,顺带砍掉了一次 475MB 的状态克隆。475MB 的内存拷贝,在一个内存本来就紧张的系统里,砍掉它比省下那 3.5 毫秒重要得多。

双缓冲的层加载,一边算一边把下一层读进来。128 字节对齐的 CPU worker 计数器,八线程下快了 3.6%。

厉害了,这一整套下来,没有一项是在改模型。全是在跟内存层级和 I/O 调度较劲。

这就是我说的,Deltafin 表面上是个跑大模型的项目,骨子里是个存储工程项目。

还有一组对比我觉得特别值得单拎出来说,因为它把这件事推到了极端。

Deltafin 给你两种装法。全量装,1.7TB 硬盘,下载 5 到 10 小时,之后 14.6 秒一个 token。流式装,215GB 硬盘,下载半小时,之后没命中缓存的部分,3 分钟以上一个 token。

同一个模型,同样的权重,同样的机器,同样的算法。速度差了十几倍。

差别只在于,那 25.8GB 的专家字节,是从本地 NVMe 里捞,还是从 Hugging Face 的 CDN 上一个个 HTTP range 请求捞回来。

作者在文档里写得很干脆,每个 token 要读 16 个专家乘 92 层等于 25.8GB,从本地磁盘大概 4 秒,走网络就是几分钟,这一个事实就是这两列的全部区别。

我特别喜欢这种写法。没有任何修辞,就是把因果链摆在你面前,让数字自己说话。

而且他还很实在地提醒,流式模式下如果你挂个编程助手上去会更惨,因为那些工具的系统提示词动辄几十上百个 token,预填充阶段要触碰的专家数量暴涨,缓存没填满的时候一个请求能花掉好几个小时。他自己给这种玩法的定性是,agent 是个乐子,不是工作流。

坦率讲,一个开源作者愿意在 README 里主动写「别拿我这个当生产力工具」,这种态度现在不多见了。满屏都是「生产级」「企业就绪」的时代,有人肯写「这是一个研究性产物,不是一个实用的聊天配置」,我觉得挺珍贵的。

回到 HN 评论区那个账。100 万 token 要跑 3.2 年,API 只要 3 美元。还有人补刀,算上电费,本地推理可能比云端更贵。

我非常理解这种反应。你不是在做研究,你是个想把活干完的工程师,手边有能用的 API,为什么要伺候一台吭哧吭哧读了一整夜硬盘、就为了憋出一段话的机器。这个质疑站得住,而且站得很稳。

评论区替它辩护的方向也有几个,无人值守的批处理跑一整夜、隐私敏感的场景、把人类造出来的东西完整保存在自己手里的那种执念。反驳的人说这些都太虚,拿不出具体的时间、token 数和使用场景。

这一来一回我看完,觉得两边其实在聊不同的东西。

反对的人在问,这玩意今天能不能用。 支持的人在说,这条路以后走不走得通。

我自己的判断偏后者,但理由跟他们不太一样。我觉得 Deltafin 最有价值的地方,不是它今天能跑出什么,而是它把一个即将变成常态的设定,提前完整地跑了一遍。

那个设定是,模型比机器大。

这几年我们一直活在相反的假设里。模型要能装进显存,装不进就换个小的,或者量化到能装下为止。整个本地推理生态,llama.cpp、ollama、MLX 那一套,默认前提都是「先想办法把它塞进去」。

但 MoE 这个结构本身,已经把「模型有多大」和「每一步要动多少」这两件事拆开了。K3 是 2.8T,可每个 token 真正要碰的是 25.8GB 的专家加上 53GB 的主干。这个比例只会越来越悬殊,因为让模型变强最省事的路子,就是往里加专家,而加专家几乎不增加每一步的计算量。

那么早晚会走到一个地方,模型的总容量彻底不再是一个「你能不能装下」的问题,而是一个「你有多少存储、多少带宽、调度得多好」的问题。到那个时候,本地推理这件事的技术核心,就从模型压缩变成了存储和调度。

Deltafin 是这条路上一个很早、很粗糙、但完整跑通了的样本。它的价值在样本本身,不在样本的速度。

如果你也在琢磨本地部署,或者在给公司选模型,我自己从这个项目里顺出来几条判断方法,不敢说都对,但至少是可以拿去当尺子用的。

最要紧的一条,别再拿总参数量当第一个坐标了。看激活参数量,看每个 token 实际要读多少字节。一个 2.8T 的 MoE 和一个 70B 的稠密模型,前者的每步字节吞吐可能还更小。这两个数在正经的模型卡上都能查到,如果查不到,那本身就是个信号。

顺着这个再往下,把「常驻的那部分能不能装进内存」当成一条硬线来卡。这条线的两侧是两个完全不同的世界,线内是正常推理,线外是每步重读。DGX Spark 那组数据就是这条线的活样本,同一台机器,仅仅因为主干从 107GB 变成 53GB,预加载等待从 676 秒塌到 18 秒。

所以量化决策也别只盯着精度损失。在贴近内存上限的场景里,量化释放出来的缓存空间,收益可能远大于它省下的那点算术。这个收益在纸面 benchmark 上是看不见的,只有在真实的 I/O 压力下才会显形。

还有个特别好用的笨办法。你要评估任何一个本地推理方案,直接问对方要性能剖面,计算多少秒、I/O 多少秒、等待多少秒。给不出这三个数的,基本可以认为他自己也没搞清楚卡在哪。

最后一条我觉得最容易被忽略。硬盘在推理链路里的地位,已经不是「存权重的地方」了,它是执行路径的一部分。以后选机器,NVMe 的持续读带宽这个指标,在 MoE 时代的分量只会一路往上爬。

我脑子里一直有个画面。

写这个项目的人,多半是在某个夜里,把 1.7TB 的权重下完,敲下第一条命令,然后就那么坐着,看着终端一个字一个字地往外蹦。十四秒一个字。他大概也知道这没什么实用价值,但他就是想看看,这台放在桌上的、四五年前的机器,到底能不能把一个 2.8 万亿参数的东西撑起来。

然后他把每一个数字都老老实实记下来了,包括那个难看的 0.0687,包括六次完整跑测的中位数和波动范围,包括那句「这是一台老掉牙的 M1,不是新款 Max 或者 Ultra」。他甚至在 README 结尾很客气地写,如果你有更新的 Mac、有 128GB 以上的机器,请把你的数字发给我,我们真的很想看看。

有点子牛逼。这个态度比那 0.0687 值钱多了。

我们这行现在特别不缺漂亮的数字,缺的是有人愿意把难看的数字连同它的成因一起摊开。前者只能让你转发,后者能让你判断。

回到开头那 14.6 秒。它现在读起来还是个笑话,一分钟四个字,谁受得了。但把它拆开之后你会发现,这 14.6 秒里没有一秒是浪费在愚蠢上的,每一秒都精确地对应着一个物理约束。1 秒的乘法,2 秒的注意力,3 秒的搬运,7.5 秒的硬盘。

它不是慢,它是把慢的原因写清楚了。

而这些约束,都是会随着硬件往前走而松动的东西。内存会变大,NVMe 会变快,统一内存的带宽每一代都在涨。今天需要 14.6 秒的这件事,五年后可能就是一秒钟的事。

到那个时候回头看,这个 124 分的帖子,大概就是一块被踩过的、写着日期的礁石。