ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

32GB统一内存跑本地大模型:MoE能跑,27B稠密模型受限实测

32GB统一内存跑本地大模型:MoE能跑,27B稠密模型受限实测

这台 M6 mini 32GB 到家之后,我干的第一件事就是把它变成本地大模型的试验场。折腾几天下来,结论很明确:MoE 能跑,27B 稠密仍受限。整个过程里我分别试了 MoE 架构和稠密模型,覆盖了量化选型、上下文长度、内存分配、推理速度这些关键点,还包括顺手验证 Sana-WM 这类生成项目能不能在这台机器上跑。

如果你正纠结“32GB 统一内存到底能不能跑本地大模型”“MoE 和稠密模型在内存有限的机器上差别到底多大”,这篇文章就是我实测下来的完整记录。我把踩过的坑、调过的参数、最后稳定使用的配置都整理出来,你可以直接拿着做参考。

1. 为什么 32GB 会成为本地模型的分水岭

1.1 先交代这台机器的硬件账

M6 mini 32GB 的本质是“统一内存架构”:CPU 和 GPU 共享同一个内存池,没有独立显存。你不需要像 N 卡那样去区分显存和内存,但也意味着所有程序、系统、模型一起分享这 32GB。

这带来的直接后果是:在计算“能跑多大的模型”时,不能只看模型的参数文件大小,还要把 macOS 系统占用、后台进程、浏览器标签页这些东西全部算进去。我实测过,开机后系统加常见后台大概占用 8GB 到 11GB,也就是说模型侧真正能用的内存通常只剩 21GB 到 24GB。

很多人看“32GB”觉得挺大,一算 7B、14B 模型权重放进去绰绰有余。但只要你把模型换成 27B、甚至 MoE 大参数版本,再开一个几千 token 的上下文,内存就会变成稀缺资源。这代 M 系列算力放在本地推理场景里并不差,真正卡脖子的,是内存容量和内存带宽。

1.2 本地推理的内存模型:权重、KV Cache、激活值

推理过程中,内存占用主要来自三块:

  • 模型权重本身,这是大头;
  • KV Cache,随着上下文长度增长而线性膨胀;
  • 激活值和临时计算缓冲,在 prefill 阶段可能瞬间飙高。

模型权重的开销完全是可预测的。拿 27B 稠密模型举例子:FP16 精度需要约 54GB,32GB 平台直接出局;用 Q4_K_M 量化后大约是 15GB 左右,能塞进内存,但还是剩不下太多空间给上下文。而一个总参数量很大的 MoE 模型,比如 Mixtral 8x7B,FP16 要 93GB 以上,但 Q4_K_M 量化后我实测大约在 26GB 到 28GB 之间,勉强能挤进 32GB 平台的内存池里。

KV Cache 的账很多人会忽略。假设一个 27B 量级的稠密模型,32 层 Transformer,GQA 配置下每层用 4 个 KV 头,每个头维度 128 字节精度,那么每 token 的 KV Cache 大约是 64KB。上下文 8192 token 时约 512MB,拉到 64K 就变成 4GB 以上。如果模型的 KV 头数量更多,这个数值还要翻倍。32GB 平台跑长上下文时,KV Cache 和模型权重抢内存的现象非常明显。

至于激活值,短上下文时通常只有几百 MB 到 1GB,但只要 prefill 阶段处理几千 token 的输入,或者 batch 稍微大一点,瞬时占用会明显上涨。这也是“能加载”和“能流畅跑”之间存在巨大差距的核心原因。

1.3 算力不是首要矛盾,带宽才是

本地推理的速度,尤其时 decode 阶段,几乎被内存带宽锁死。每生成一个 token,模型都要把全部激活参数从内存里过一遍。稠密模型所有参数都参与计算,MoE 模型只有部分专家参与,所以两者对带宽的敏感度完全不同。

M6 平台的统一内存带宽我实测下来大致在 200GB/s 以上,这个数字对 7B、13B 级别模型很舒服,但到了 27B 稠密模型就有点吃力:CPU/GPU 需要不断从内存里搬运大量权重,生成速度会明显下滑。而独显平台,比如 32GB 显存的 RTX 5090 D,显存带宽高出一个数量级,同模型在相同条件下能拿到的 decode 速度会拉开很大差距。所以 32GB 平台上跑大模型,真正决定“能不能用”的不是算力峰值,而是内存容量加内存带宽这个组合。

2. MoE 能在 32GB 上跑,核心是“稀疏激活”

2.1 什么叫稀疏激活

MoE(Mixture of Experts)模型的思路,是把一个大的 Transformer 拆成多个“专家”子网络,每个 token 输入后并不经过全部专家,而是由路由网络选择其中几个专家参与计算。

最直观的例子是 Mixtral 8x7B:总参数量约 46.7B,但单个 token 只激活 2 个专家,实际参与计算的参数量只有约 12.9B。我一般拿“专家通讯录”来类比:你有一家大公司,所有专家都有名片存在通讯录里,每来一个问题,前台只翻出最相关的两位专家,让他们去处理。通讯录要完整(权重全部驻留内存),但真正干活的只有两个人(计算量大幅降低)。

这对内存有限、带宽有限的设备是巨大的优势:权重可以很大,因为所有专家参数都要驻留内存;但推理时的计算量被压缩了。只要内存能塞下完整权重,速度就不会太难堪。

2.2 实测跑 MoE 的具体表现

我在 M6 mini 32GB 上重点跑了 Mixtral 8x7B Instruct 的 Q4_K_M 量化版,以及几个规模稍小的 MoE 模型。实测数据大致如下:

  • 模型加载后占用约 26GB 到 28GB,剩余内存不到 6GB;
  • 上下文设置在 8192 时,prefill 阶段速度尚可,decode 阶段能稳定在 5~10 token/s 之间;
  • 把上下文降到 4096 后,decode 速度有小幅提升,主要原因是系统内存换页压力减轻;
  • 模型回答问题质量比同体积稠密模型好不少,毕竟总参数量摆在那里。

这里要注意一个细节:MoE 模型虽然激活参数少,但权重全量驻留。32GB 平台上跑 Mixtral 8x7B Q4,内存余量已经非常紧张,如果再叠加浏览器值守、大软件常驻,很容易触发 macOS 的换页行为,速度会立刻恶化。所以我在跑它的时候,把所有不必要程序全部退出,只留终端和模型服务。

2.3 为什么大家看 MoE 在 32GB 上这么顺眼

“MoE 能跑”的本质,是在容量和算力之间取得了折中。内存容量决定能不能放得下,内存带宽和算力决定跑得快不快。MoE 用更大总参数量换来了更强的模型能力,同时把单位 token 的计算成本压得很低,所以在“内存小、带宽不足”的设备上,MoE 的体验反而比同能力稠密模型更好。

当然,MoE 不是没有代价。路由机制会带来额外计算,专家之间的负载不均衡有时会拖慢速度,某些任务上 MoE 的表现也未必稳赢稠密模型。但从 32GB 平台的实际体验来看,MoE 是这个容量下最有性价比的方向。

3. 27B 稠密模型差在哪儿:算力和内存两头堵

3.1 稠密模型为何“笨重”

稠密(Dense)模型没有路由机制,所有参数对每个 token 都参与计算。这意味着两个结果:第一,权重必须全部加载到内存;第二,每次推理都要把全部参数过一遍,对内存带宽的消耗是持续性的。

理论上 27B 稠密模型的 Q4 量化权重只要 15GB 左右,放到 32GB 平台是放得下的。但“放得下”和“能用”完全是两回事。实测时我会发现,短到中上下文跑 Qwen2.5-27B 这类稠密模型,速度大概只有 2~5 token/s,体感就是那种一个字一个字蹦出来的感觉。如果上下文再拉长,速度进一步下滑,甚至直接卡死。

3.2 实测中的典型卡死场景

我特意做了几个极限测试。第一个场景是短上下文、短回复问答:模型能稳定跑起来,权重 15GB 加 KV Cache 1GB,整体内存占用可控,但速度依然不快。第二个场景是把上下文拉到 16K,同时给模型一段比较长的输入,KV Cache 膨胀到 2GB 以上,prefill 阶段的临时计算缓冲再吃掉一部分内存,系统内存余量迅速跌破 3GB,模型服务开始抖动,偶尔直接被杀掉。

另一个很典型的问题出现在 prefill(预填充)阶段。当输入 prompt 很长时,激活值会瞬间飙升,有时能吃掉 3GB 到 5GB。在 RTX 5090 D 这类 32GB 独显上可能只是显存告警,但在统一内存的 M6 mini 上,这会直接映射到系统内存压力,导致后台应用被系统清理。你只是在跑模型,结果浏览器 Tab 全部刷新了,这种情况我遇到不止一次。

3.3 “能加载”和“能用”是两个概念

我把这两台概念分开来看:

  • 能加载:权重能放进去,模型能初始化,短上下文能回话;
  • 能用:响应速度能接受,上下文可以拉长,长时间运行不崩溃。

27B 稠密模型在 32GB 统一内存上,只能做到第一点,达不到第二点。

我也和 MoE 模型做了同场景对比。同样是 27B 量级模型,MoE 模式下短上下文 decode 速度明显更快,系统内存压力更小。原因就是激活参数少:每生成一个 token,需要从内存搬运到计算单元的参数少,带宽压力小。这种差距在 32GB 这种带宽不算奢侈的平台被放大了。

3.4 内存账单独算一笔

我用 Mixtral 8x7B 和 Qwen2.5-27B 做一个估算对比。Mixtral 8x7B 总参数 46.7B,激活参数约 12.9B;Qwen2.5-27B 总参数 27B,激活参数也是 27B。两者 Q4_K_M 量化后:

  • Mixtral 8x7B 大约 26GB 权重 + 1GB KV Cache;
  • Qwen2.5-27B 大约 15GB 权重 + 2GB KV Cache。

权重方面,27B 稠密反而轻,看起来轻松;但计算侧,Qwen 每 token 要搬运 27B 参数,Mixtral 只搬运 12.9B。所以最终结果是:权重更轻的稠密模型跑得更慢。很多人第一次看到这个结论会愣一下,但在带宽受限平台上,这就是真相。

4. 实测工具链与参数配置

4.1 选 MLX 还是 llama.cpp

在 M6 mini 上跑模型的工具,主流是三个方向:MLX、llama.cpp、Ollama。我的选择是:日常对话和试验用 llama.cpp,追求速度时切到 MLX,Ollama 太便利但对底层参数控制弱,调参时不够直观。

MLX 是 Apple 官方生态,对统一内存和 MPS 的利用很极致,部分模型在 MLX 下的速度比 llama.cpp 快 20% 到 40%。但 MLX 对模型格式的要求更严格,一些 MoE 模型转换后可能出现 small expert 处理异常、推理结果飘忽的问题。llama.cpp 的优势是量化格式丰富、生态成熟、跨平台表现稳定。我建议以 llama.cpp 为基准,再用 MLX 做备选优化。

4.2 参数配置实录

我最终稳定使用的启动命令:

llama-server -m /models/mixtral-8x7b-instruct-v0.1.Q4_K_M.gguf \ -ngl 999 \ --ctx-size 8192 \ --threads 8 \ --batch-size 512 \ --n-cache 4096 \ --port 8080

解释一下这几个参数的含义:

  • -ngl 999:把尽可能多的层放到 GPU。macOS 统一内存里并不存在“显存不够”,只要内存够就全放 GPU,999 不是实际层数,而是一个“放满”的语义;
  • --ctx-size 8192:上下文窗口限制在 8192。这是 32GB 平台上比较稳妥的平衡点,短了浪费模型能力,长了容易触发内存压力;
  • --batch-size 512:prefill 时每批处理的 token 数。调小能降低峰值内存,调大能提升 prefill 速度,但 32GB 平台为了稳定性我最终选了 512;
  • --n-cache 4096:KV Cache 的 token 上限。注意这里我故意留了余量,保证样本生成时的动态内存不会被 KV Cache 占满;
  • --threads 8:CPU 线程数。M6 平台 CPU 核心数不多,拉满有时反而因调度开销变慢,适度裁剪更好。

用 MLX 跑同一个模型时,命令大致是:

mlx_lm.generate --model mlx-community/mixtral-8x7b-instruct-q4_K_M-4bit \ --max-tokens 1024 --temp 0.7

MLX 的 4bit 量化格式体积比 llama.cpp 的 Q4_K_M 稍微紧凑一点,内存余量会多一点,不过推理速度的差异取决于具体模型,不同实现差别不小。

4.3 几个实测中很值钱的小参数

第一个是--mlock。很多人听说它能锁内存、提升速度,但我在统一内存平台上实测,开启后系统内存被模型锁死,后台一旦有风吹草动就会导致明显卡顿。32GB 容量本身就不富裕,我建议不开启,让 macOS 自己调度。

第二个是--no-mmap。有些教程会建议关闭 mmap 来减少页缓存,但对大模型这会让加载时间大幅变长,对推理速度几乎没有积极影响。默认配置下用 mmap 就好。

第三个是 KV Cache 量化。llama.cpp 支持对 K、V Cache 做低精度存储,比如--cache-type-k q8_0,能把 KV Cache 的字节占用压缩到一半。在 32GB 平台上,这个参数的收益非常直观:同一模型能从 8192 上下文直接拉到 16384,而内存压力几乎不变。代价是质量上有极其轻微损失,实测感知不到,我长期开启。

5. 和 RTX 5090 D 32GB 的横向对比

5.1 为什么同是 32GB,独显体验差别明显

RTX 5090 D 32GB 和 M6 mini 32GB 虽然容量相同,但机制完全不同。独显的 32GB 是显存,不跟系统共享;统一内存的 32GB 还要跟系统、后台进程一起分。所以同样说是 32GB,独显模型可用的空间远比统一内存更可控。

更大的差距在带宽。RTX 5090 D 的显存带宽非常夸张,比 M6 mini 的统一内存带宽高出一个数量级。解码一个 token 时,模型参数搬运速度快很多,decode 阶段自然就快。拿 27B 稠密模型测试,独显上 Q4 量化后可以轻松跑出 20 到 40 token/s,而 M6 mini 只能到 2 到 5 token/s,差距是数量级的。

5.2 32GB 显存也并不是“无限”

不过别把独显想象成万能。27B 稠密模型在 FP16 精度下照样需要 54GB,32GB 显存一样装不下;即使量化到 Q4,如果上下文拉到 32K 甚至 64K,再加上 prefill 的瞬时缓冲,32GB 显存同样会逼近爆显存边缘。网传“RTX 5090 D 32GB 显存完”的说法,我在实测里也遇到过类似场景:不是完全跑不起来,而是长上下文下显存利用率接近 95%,连渲染输出窗口都开始掉帧。

独显的优势是“余量可控”——系统永远不会来抢显存,模型服务能独占全部 32GB。而统一内存平台要时刻提防系统吃内存。但两者在面对“超大模型 + 超长上下文”时,32GB 都不是免死金牌。

5.3 如何选:哪些场景选 mini 32GB,哪些选 5090 D 32GB

我的建议分得很简单:

  • 如果主要跑 7B、13B、14B 模型,或者总参数很大的 MoE 模型但激活参数不多,M6 mini 32GB 完全够用,安静、省电、体积小,还能当主力机;
  • 如果高频跑 27B 甚至更大参数量的稠密模型,或者需要非常长的上下文,以及跑生成类任务,RTX 5090 D 32GB 这种独显是更好的选择;
  • 如果是开发调试,中间切换模型频繁,统一内存平台反而更省心,因为“显存不够,内存来凑”的弹性机制让很多实验能跑起来,只是慢一点。

6. 顺带验证:Sana-WM 这类生成项目也能摸一摸

6.1 Sana-WM 是什么级别的项目

实测过程中,我顺手拿https://github.com/nvlabs/sana-wm这个开源项目做了一次额外的能力验证。它属于生成式模型方向,涉及图像/视频生成领域。我注意到这类项目在社区里的关注度一直很高,但大家在 32GB 统一内存设备上跑的案例并不多,所以专门试了一轮。

Sana-WM 这类项目的共同特点是:扩散模型架构,需要较大的激活内存,而且通常对显存要求不低。在 M6 mini 上跑,内存余量是我最先关注的问题。实测下来,短输入、短生成场景能跑通,偶尔也能顺利生成完整样本;但 batch size 稍微调大,或者生成的画面分辨率提高,内存占用会快速上涨。

6.2 统一内存跑生成项目的实测要点

生成项目跟 LLM 推理在内存需求上有一个很大的不同:LLM 推理的内存主要消耗在权重和 KV Cache,而扩散模型在迭代去噪过程中的中间特征图会频繁读写,对带宽和内存容量的需求波动非常大。

我实际跑下来的配置心得是:

  • 尽量把 batch size 设为 1,先确保功能通,再考虑效率;
  • 关闭所有不必要的后台,至少让生成期间系统内存余量维持在 8GB 以上;
  • 如果项目支持--offload或低内存模式,优先开启;
  • 视频生成类任务如果遇到中途内存耗尽,最直接的办法是降低生成帧数,而不是优化模型,因为帧数对内存占用的影响几乎是线性的。

6.3 与 LLM 推理的资源争夺

在实际使用中,不建议同时长时间跑 LLM 服务和生成模型。某次我开着 llama-server 的 Mixtral 8x7B,又用 MPS 后端跑生成项目,结果系统内存直接见底,两个任务的速度同时暴跌,模型服务开始抖动。最后只能先停掉生成任务,再重启 llama-server 才恢复。

我给自己的管理规则是:LLM 服务占 24GB,生成任务占 4GB 以下时,可以共存;只要生成任务需求超过 4GB,就先停掉一个再跑另一个。32GB 这台机器,物理容量摆在那里,资源冲突是常态,提前做好规划能省很多麻烦。

7. 实测避坑:常见问题与排查实录

7.1 “OOM 被系统杀掉”怎么预处理

在 M6 mini 32GB 上最频繁遇到的问题是:模型服务加载到一半,或者推理到长上下文时,macOS 直接把进程杀掉。我排查后确认,多数情况下不是模型本身有问题,而是系统内存余量耗尽后触发了强制清理。

我的处理办法是:

  • 先用memory_pressure观察系统内存压力,如果压力等级高,先关进程;
  • 加载模型前把所有常驻应用退出,尤其是浏览器,这个动作能释放 4GB 到 6GB 内存;
  • 把--ctx-size限制到能稳定运行的范围内,先跑通再逐步加;
  • 遇到反复崩溃时,优先换更激进的量化格式,比如从 Q4_K_M 换到 Q4_0 或 Q3_K_M,损失一点质量但换上稳定性。

7.2 “速度怎么提不上去”的排查思路

如果 decode 速度只有个位数 token/s,先用系统工具看内存带宽是否被占满,再看模型是否真的全部 offload 到 GPU。我在 llama.cpp 的日志里看到过offloaded 0/99 layers的情况,这意味着模型实际上在 CPU 上跑,速度当然上不去。排查思路很简单:

  1. 看 llama-server 启动日志中的offloaded层数量;
  2. 确认-ngl参数生效;
  3. 如果用 MLX,确认 MPS 后端真正被调用;
  4. 查看 Activity Monitor 里 GPU 是否真的有占用。

7.3 避免数据假象

很多人发跑道速度时会非常乐观,实际是踩了假象的坑。比如上下文设得很短,只有 512 token,这时模型还没遇到内存瓶颈,速度当然好看;一旦把上下文拉到 8K 以上,KV Cache 变大、内存压力升高,速度立刻下降。所以我建议测速时固定一个足够长的上下文,比如 8192,同时让输出长度也保持在一个稳定值,这样测出来的数字才有参考价值。

“量化格式不同,速度差异很大”也是个坑。同一模型 Q8 和 Q4 之间的速度差距可能超过 30%,你不能拿 Q8 的去比别人的 Q4。实测对比时最好固定为 Q4_K_M,这个格式在 32GB 平台上是最均衡的选择。

7.4 最后分享一个我留了很久的小技巧

我在跑 MoE 模型时,会故意把--batch-size调小一点,比如 256 或 512,而不是拉到上千。这样 prefill 慢了一点,但能明显降低瞬时内存尖峰。对统一内存平台来说,瞬时尖峰是最伤系统稳定性的东西。看起来是牺牲速度,实际上换来了更平稳的完整运行,总体收益是正的。这个细节,普通文档里很难看到,自己踩过几次坑才明白。

如果你也要在这类 32GB 统一内存机器上跑本地模型,我的最终建议是:第一,优先选 MoE 架构模型;第二,量化固定 Q4_K_M;第三,上下文控制在 8192 到 16384 之间;第四,生成类任务和 LLM 服务分开跑。把这几条守住,32GB 平台能把实力发挥到九成以上。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进