跳到主要内容
80B 模型、4.3GB 内存——Swiftlet 把存储当内存用

80B 模型、4.3GB 内存——Swiftlet 把存储当内存用

阿舟
阿舟

· 阅读约 5 分钟

看到 Swiftlet 的 README 之前,我对「本地跑大模型」的印象还停在内存军备竞赛上。然后它甩出这么一行:Qwen3-Next-80B-A3B,42GB 磁盘,4.3GB 峰值内存,M5 上每秒 4.5 到 5 个 token。

我下意识以为这项目把磁盘和内存两栏抄串行了。结果并没有。

Swiftlet 是一个 Swift + Metal 写的 runtime,专门在 Apple 设备上跑 Qwen 那个 MoE 混合模型家族,思路一句话:dense 核心常驻内存,路由到的专家权重从存储按需流式加载。原理说穿了不值钱——MoE 每算一个 token 只激活一小部分参数,35B 那个每层 256 个专家挑 8 个,80B 那个每层 512 个专家挑 10 个。

需要常驻的永远是 dense 那一坨:注意力、DeltaNet 投影、路由器、共享专家、embedding。35B 在 4-bit 下加起来 1.3GB,80B 也就 2.5GB。剩下的专家,用的时候从 SSD 现取。

所以本地推理的成本等式,跟「模型多大、内存多大」早就不是一回事了。真正的变量是两件事:常驻最小集能做多小,以及每 token 的取数路径有多快。过去默认的玩法是模型整个塞进内存——权重要占地方,KV cache 越长越大,上下文窗口还来抢剩下那点内存。Swiftlet 干的就是把这层窗户纸捅破:不该常驻的,别让它常驻。

这里还有个不容易看见的细节:75% 的层用的是 Gated DeltaNet 线性注意力,recurrent state 固定大小。说人话就是 KV cache 不随上下文长度增长,context 拉多长,内存占用都是同一个数。权重是流的,KV cache 是恒定的,这两件事叠一起,才是那个 4.3GB 的完整解释。

真正硬核的是取数路径怎么做到不拖后腿。专家权重被重打包进一个叫 .qpack 的容器,每个专家按固定步长排成一个 blob,于是取一个专家就是一次 pread,展开成代码大概长这样:

let offset = blobBase + expertIndex * expertStride
pread(fd, &buf, expertSize, offset)

没有索引跳转,没有碎片化读取,一个 offset 一个 size 完事。粗暴,但有效。

专家当然不能每次都现读。一个 bounded pool 缓存热点专家,LFU 加 recency 驱逐,作者报的命中率在 43% 到 70% 之间。这里我停下来多看了一眼:命中率波动这么大,吞吐却基本不变,说明当前瓶颈根本不在取数。作者也直说了,decode loop 现在是 dispatch bound,不是 IO bound。画外音:一个从 SSD 读参数的系统,瓶颈居然不在 SSD——这大概就叫把读盘驯化了吧。

性能数字放在一起,其实是一道明确的选择题:

4-bit 35B:18GB 磁盘,2.6GB 内存,7-11 tok/s
8-bit 35B:34GB 磁盘,7.6GB 内存,3.5-4 tok/s
4-bit 80B:42GB 磁盘,4.3GB 内存,4.5-5 tok/s

同样的模型,量化深度换来的是磁盘、内存、速度三者此消彼长。8-bit 那个版本磁盘翻倍、内存快三倍、速度掉一半多,换来的东西按作者的说法,是写作质量——4-bit 在长文任务上会有重复伪影,8-bit 把这个毛病治掉了。如果让我选,我会为后者买单。现阶段本地模型长文输出那股「复读机」劲儿,比慢更劝退。慢是可以等的,复读没法忍。

但有个结构性的坎,谁优化也填不上:每 token 只有 3B 左右参数活着。结果是模型聊天、写东西的时候像大模型,回忆事实的时候像小模型。你让它顺文案、聊观点,它有模有样;你问它一个具体年份、一个具体 API 的边界行为,它大概率一本正经地编一个给你。这不是优化问题,是结构问题。别拿它当知识库使,它不是。

让我真正改观的,是它对待「正确」的方式。每个层都有对照 mlx-lm 参考实现的 fixture,f32 和 int4 两种形态逐层验证;快速和标量两版 GPU kernel 要求输出完全一致。这两个测试放在任何正经 runtime 里都是底线,但在开源 AI 项目里,稀缺得像会写文档的实习生。大多数项目的标准是「能跑出 token」——至于跑出来的东西跟参考实现差多少,没人知道,也没人想知道。

作者把早期那个 397B 流式跑通的概念验证归功于 ANEMLL,但 Swiftlet 自己的代码是从零起的——Swift 加 Metal,大约一万行。从零写意味着每个 kernel 都得自己回答「我做对了没」这个问题,而不是从哪抄一段改改能跑就收工。五百多个 star,放在今天不算多,但看一眼测试那节的密度,就知道这项目没把力气花在包装上。

至于 iPhone 17 上 35B 那个 2.5GB、约 1 token/s——别当产品看,它是个方向信号。1 token/s 连打字机都嫌慢,可作者的目标本来就落在「这类模型以可安装 app 的形式出现在基础款 iPhone 上」。配套还给了四种用法:Swift package、CLI、loopback server、iOS app。那个 server 讲的是 OpenAI 兼容的 chat-completions API,跑在 loopback 上,意思是任何现成的聊天前端都能直接接进这个本地模型,不用专门写适配。

把模型做成基础设施而不是 demo,这个姿态我很吃。

以后谁再跟我讲「本地跑 80B」,我大概会先问两个问题:dense 常驻是多大?每 token 激活几个专家?两个都答不上来的,基本还是拿内存硬装的老思路。Swiftlet 让我改观的从来不是「把 80B 塞进 4GB 内存」这种鬼话——它没塞,它只是肯把存储和内存之间的这笔账,掰开重算了一遍。

阿舟
阿舟

写代码写到一半开始怀疑人生,靠 AI 工具续命,顺手把踩过的坑都记下来。

查看主页 →