2026年8月7日 1 分钟阅读

手机内存装不下 60GB 模型怎么办?用 BigMoeOnEdge 把 MoE 专家按 Token 从闪存调进来

tinyash 0 条评论

手机上的本地大模型,最常见的瓶颈不是 CPU 算得慢,而是权重根本放不进内存。尤其是 MoE(Mixture of Experts,混合专家)模型:文件可能有 18GB、60GB,甚至更大;但在生成一个 token 时,路由器通常只会选择少量专家参与计算。若仍按传统方式把整个 GGUF 映射进内存,设备就会在缺页、回收和重新读取之间反复震荡:吞吐不稳定,后台应用也可能被系统清掉。

BigMoeOnEdge 选择了另一条路径:把闪存视作内存层级的一部分。它让始终需要的非专家权重保持可用;当某一层为当前 token 选出专家后,才从闪存读取对应权重切片,在矩阵乘法前送入计算。未被选中的专家继续留在磁盘上。

项目基于 llama.cpp 的公开 API 构建,并不是把 llama.cpp 整体 fork 后另起一套运行时。README 说明,量化格式、tokenizer 与聊天模板依旧由 llama.cpp 处理;BigMoeOnEdge 负责的是 MoE 专家的按需读取、缓存与统计。仓库为 Apache-2.0 许可证,主语言为 C++;最新公开版本为 v0.19.0。

不要把“模型大于 RAM”理解成单一问题

大于内存的模型至少有三种不同场景。

第一种是“远大于 RAM”:例如 12GB 内存的手机面对约 60GB 的模型,常驻加载没有现实可能。第二种是“略大于 RAM”:18~22GB 模型看似离手机可用内存不远,但普通 mmap 方式容易触发持续缺页,速度会随系统回收和温度波动。第三种是“勉强放得下”:即使模型能加载,仍可能挤占其它应用的内存预算。

BigMoeOnEdge 的价值不只是让第一种场景“勉强跑起来”,也在于为后两种场景明确 RAM 上限。它的专家缓存可以限制在可承受范围内;缓存命中时直接复用 RAM 中的专家,未命中时再读闪存。这样,开发者可以把资源约束从“操作系统随机回收”改成“应用显式预算”。

关键路径:路由、读盘、缓存和计算必须分开看

最小可复现的主机端流程如下。模型文件需要自行准备;示例中的 GGUF 名称只是路径占位,不能直接当成下载命令。

git clone --recursive https://github.com/Helldez/BigMoeOnEdge.git
cd BigMoeOnEdge
scripts/build-host.sh

build/cli/bmoe-cli -m Qwen3-30B-A3B-Q4_K_M.gguf --moe-stream \
  --cache-mb auto --cache-ceil-mb 4000 --io-threads 4 -t 4 -n 48 \
  --chatml -p "Explain MoE routing."

--moe-stream 开启按路由读取专家;--cache-mb auto 让程序估算缓存,--cache-ceil-mb 4000 则防止自动估算占用过多内存。--io-threads 4 是并行读取通道,不代表越多越快:闪存带宽饱和后,继续增加通道可能没有收益。

项目默认尝试 Direct I/O,以避免操作系统页缓存再保留一份专家权重;如果平台不支持,才会回退。--overlap 则试图让下一批读取与当前层计算重叠,隐藏部分 I/O 延迟。不过这项能力有明确边界:README 说明它需要 llama.cpp CPU MoE 内核中的一个很小的可选 hook,并非完全不依赖补丁的路径。

更值得养成的习惯是打开运行时观测,而不是只盯着 tok/s:--progress 会分出每个 token 的闪存 I/O、缓存管理和计算耗时,并报告缓存命中率与读取字节数;--csv 可额外输出内存相关指标。若缓存命中率低、每 token 的读取量居高不下,应先调整缓存预算;若 I/O 时间已很低,瓶颈可能已转为 CPU 或内存带宽,盲目增加 I/O 线程不会解决问题。

一个实用的排障顺序是:先固定模型、量化方式、提示词和生成长度;再用关闭流式的 mmap 结果作为基线;然后只开启 --moe-stream,确认它能稳定生成;最后一次只调整一个变量,例如缓存上限或读取通道数。每次记录 tok/s 之外,还要记录闪存读取量、缓存命中率、设备空闲内存与温度。否则两次“更快”的实验可能只是一个缓存已经预热,或系统恰好没有回收内存。

缓存也并非越大越好。过小会导致专家不断换入换出,表现为命中率低、读取量大;过大又会与始终驻留的密集权重和 Android 系统争夺内存。--cache-ceil-mb 的意义正是在自动分配时保留硬边界。对生产或可重复演示,优先使用明确的固定预算;对探索机器的可用上限,才考虑 auto 加 ceiling 的组合。

还要区分“存储能读多快”与“端到端能生成多快”。如果 telemetry 显示读取已经被计算掩盖,继续提升存储并不会线性提高吞吐;如果计算残余时间很高,也可能是 CPU 降频、内存带宽或线程配置问题。BigMoeOnEdge 的思路不是给出一组万能参数,而是让这些差异在每 token 的数据中可见。

多分片 GGUF 与设备路径的两个细节

超大 GGUF 经常以多分片形式发布。项目支持将分片放在同一目录后直接指向第一片;不需要先把它们合并成一个大文件。这一点对移动设备尤其重要:合并本身会额外占用接近模型大小的磁盘空间,也增加了下载和校验步骤。

在 Android 上,文档还特别指出模型应位于真实文件系统路径,例如 /data/local/tmp/...,而不是 /sdcard。这是一个容易忽略的运行边界:按需读取依赖底层文件访问特性,外部共享存储路径并不等价于应用可稳定执行 Direct I/O 的位置。准备模型前应同时核对可用磁盘、文件系统位置和分片完整性,而不是只看手机标称容量。

“流式”不等于“为了速度偷偷少算”

这类方案最容易被误解的地方,是把所有优化都当成同一性质。BigMoeOnEdge 明确区分两组开关。

流式读取、专家缓存、Direct I/O 与 I/O/计算重叠只改变权重如何抵达计算路径,不应改变模型数学计算。项目用合成小型 MoE 做 ctest 字节一致性门禁:流式模式输出必须与专家全量常驻时一致,并覆盖缓存驱逐、多轮会话、异步路径和多分片边界。

另一组则是有意的质量—速度取舍。例如 --n-expert-used 让每个 token 使用少于模型原始路由数的专家;--drop-cold-experts 会在缓存未命中且专家路由权重较低时跳过读取;--route-ahead 提前提交后续层的路由选择。这些设置能减少读取或计算,但会改变模型实际计算,不能与“无损流式”混为一谈。

因此,验证基础实现时应先运行项目给出的门禁:

cd build
ctest --output-on-failure

该测试需要带有 gguf 包的 Python 3 环境。它验证的是流式管线与常驻管线的一致性,不是替你证明某个有损配置在业务任务上的质量。

性能数字要带着设备条件读

项目 README 记录了一台 12GB RAM、UFS 4.x 存储手机上的测试,使用 256-token greedy decode,并强调不同表格行可能来自不同会话。比如约 60GB 的 gpt-oss-120b 在该设备上的 mmap 基线为 0.09 tok/s;流式、2GB 缓存、8 个读取通道的全专家配置记录为 1.3 tok/s。Qwen3-30B-A3B 的最佳全路由流式记录为 5.2 tok/s,但这不是所有手机、所有温度和所有模型的承诺。

更重要的是,项目把“少用专家”标成有损模式。例如同一张 gpt-oss-120b 表中,使用 2 个专家的 2.2 tok/s 结果不能拿来与全路由结果宣称同等质量。把设备、模型量化、缓存预算、激活专家数和测试协议一起记录,才是可比较的性能报告。

适合谁,以及不适合谁

BigMoeOnEdge 适合愿意为本地推理做工程调优的人:你有大于 RAM 的 MoE GGUF、能接受把闪存吞吐与缓存命中纳入调参,也需要明确地区分一致性保障和有损加速。Android 是项目重点,仓库也提供桌面构建路径;Linux CI 覆盖构建和一致性门禁。

如果模型本来就能舒适地放进内存,项目文档的建议很直接:继续使用常驻加载。流式读取引入了缓存、I/O 调度和设备状态等变量,不是所有本地推理都值得增加这层复杂度。它真正解决的是一个具体边界:当 MoE 的稀疏激活让“每 token 需要的权重”远小于“模型总权重”时,怎样把这个结构特征转化为可观测、可验证的设备端运行策略。

相关链接

发表评论

你的邮箱地址不会被公开,带 * 的为必填项。