随笔

在 48GB Mac mini 上跑 27B 大模型:量化、KV 缓存与上下文的工程取舍

Hohoo.

48GB Mac mini 本地部署 27B 模型的实测记录:量化方案、KV 缓存推导、统一内存 OOM 与上下文取舍。

2026-10-26 · Hohoo

浏览统计暂不可用

本文目录 8
跳到正文 ↓

在 48GB 统一内存的 Mac mini 上跑 27B 量级的本地模型,成本最低,坑也最多。这篇记录的不是"能跑通",而是为什么这样配、为什么那样会崩——把量化、KV 缓存、统一内存三个子系统拆到参数级。

结论速览

决策点 结论 原因
模型规模 27B Q4 权重 16GB 留出 32GB 给 KV 与激活
量化方案 oQ4 类 4-bit(含异常值通道) 纯 4-bit 在长文本质量上劣化,异常值通道是折中
KV 缓存 4-bit 每 token 开销减半,长上下文救命
上下文 32K 封顶 KV 膨胀 + 激活峰值,64K 不可行
MTP 开启 多 token 预测头,花 ~2GB 换 30%+ 吞吐
定位 辅助/回退模型 64K 硬门槛决定它不能当主模型

1. 模型选型:27B 是甜点,但“Q4”不是一回事

权重内存的构成(以 4-bit + 异常值通道 + MTP 方案为例):

总权重 ≈ 主干参数(4-bit) + Embedding(通常保持 16-bit) + MTP 头(4-bit) + 输出头(16-bit)

27B 参数若全部压到 4-bit:27B × 0.5B ≈ 13.5GB。但工程上不会这么干——Embedding 和输出头对精度敏感,通常保持高位,主干里也会有少量异常值通道保持高位。最终落到 16GB 左右,是"可用精度"和"内存预算"的平衡点。

量化方案对比:

方案 权重占用 精度 速度 结论
FP16 ~54GB 基准 最慢 48GB 机器不可行
INT8 ~27GB 几乎无损 中 上下文预算被压缩
纯 4-bit(NF4 类) ~16GB 长文本偶发质量劣化 快 可用但不够稳
4-bit + 异常值通道(oQ4 类) ~16GB 接近 8-bit 快 推荐

4-bit 的核心矛盾:权重矩阵里存在少量"异常值"通道,数值幅度远大于其他通道,一刀切压 4-bit 会让这些通道量化误差爆炸。oQ4 类方案的思路是把这些通道单独识别出来保持高位——代价是几个百分比的额外内存,换来长文本质量的明显稳定。

MTP(多 token 预测)头:不是"预测下一个词",而是并行预测未来若干个 token,再用小模型逐级校验接受。吞吐提升通常 30–50%,代价是一份小头参数 + 校验计算,总内存开销约 2GB。对 48GB 机器,这笔交易值得做。


2. KV 缓存:上下文真正的成本中心

自回归生成时,每个 token 的注意力 Key/Value 都要缓存,KV 缓存随上下文线性增长——这是长上下文最大的隐性成本。

KV 每 token 开销公式:

KV/token = 2 × 层数 × KV头数 × head_dim × 每元素字节数

以典型 GQA 架构为例(48 层、8 个 KV 头、head_dim 128,代入你所用模型 config.json 的实际值):

缓存位宽 每 token 8K 上下文 32K 上下文 64K 上下文
FP16 ~192KB 1.5GB 6.3GB 12.6GB
4-bit ~96KB 0.75GB 3.2GB 6.3GB

GQA(分组查询注意力)是关键。 8 个 KV 头对 48 个查询头,KV 开销是全注意力的 1/6。如果没有 GQA,32K 上下文的 KV 缓存会直接翻倍到 12.6GB,这台机器基本告别长上下文。

由此看 64K 为什么不可行:

  • KV 64K × 4-bit ≈ 6.3GB(理论可接受)
  • 但峰值激活值、生成时的临时缓冲、以及 Metal 堆的碎片,会让实际峰值远超线性估算
  • 在 wired_limit 约束下,长上下文推理触发 Metal OOM 是必然,不是偶然

KV 4-bit 的精度代价:小数位被压掉后,长程注意力的细微排序可能出错。本地工具场景(代码补全、摘要、问答)影响可忽略;但涉及长文档精确定位的任务,需要实测验证。这也是"先用、再验"的原因。


3. 统一内存:为什么“内存还有剩”也会 OOM

Apple Silicon 是统一内存——CPU 和 GPU 共享同一块物理内存,但被划分为不同堆区:

  • CPU 堆:进程地址空间,权重加载、中间张量走这里
  • Metal GPU 堆:推理计算与 KV 缓存的主战场,受 iogpu.wired_limit_mb 约束
  • wired 限制:GPU 可占用的"已钉住"内存上限

关键坑:Activity Monitor 里显示"内存充足"不代表 GPU 堆没满。GPU 堆的可用空间由 wired 限制和已分配缓冲共同决定——OOM 常常发生在"总内存还有富余"的时候。

排障顺序:

  1. 看日志 gateway.error.log 中的 Metal OOM 行
  2. 确认 KV 位宽、上下文长度是否超过预算
  3. 检查 model_settings.json 是否生效(改完必须重启服务——KV 缓存分配器在加载时初始化,热改无效)
  4. 调整 iogpu.wired_limit_mb 前先确认这是"限制"而非"瓶颈"——曾提到 45219,重启后失效,说明该参数并非每次启动都持久化,需要逐次验证

4. 上下文长度:32768 是怎么来的

模型本身支持远大于 32K 的上下文,但实际交付被三重约束卡在 32768:

  1. 显存预算:见第 2 节,KV 线性膨胀 + 激活峰值
  2. 服务端配置:model_settings.json 中的上下文上限,是保护性封顶
  3. 工具链门槛:Agent 框架主模型要求 64K 起步,32K 直接不满足,只能降级为辅助/回退

配置层的正确姿势:按 provider 设上下文,而不是全局一个值。 全局 context_length 一个值会卡死所有模型——云端大窗口模型被压小浪费预算,本地小窗口模型被拉大直接 OOM。正确做法是每个 provider 独立配置(如 xfyun 给 262144、omlx 给 32768),框架在路由时按模型能力分配任务。

与 prompt caching 的关系:KV 缓存同时也是 prompt caching 的载体。前缀稳定时,重复 prompt 的 KV 可以直接命中复用,省掉重复 prefill 计算。这要求请求侧保持前缀结构稳定——这也是为什么"上下文工程"(预算、卸载、压缩)和 KV 量化是同一枚硬币的两面,而非两个话题。


5. 排障手册

症状 排查路径
生成突然中断、报错 看 gateway.error.log 的 Model fallback 与 Metal OOM
输出明显变慢 上下文接近上限时 KV 膨胀,检查 agent.log 每轮实际上下文
配置改了没生效 ~/.omlx/bin/omlx restart,确认服务重启后再测
长 prompt 直接崩 降上下文封顶,或临时降 KV 位宽验证
内存够却 OOM 查 iogpu.wired_limit_mb 与 GPU 堆实际占用,别信系统总内存

6. 给 48GB 机器的推荐配置

项目 推荐值 说明
模型 27B Q4(4-bit + 异常值通道 + MTP) 权重 16GB,留 32GB 给 KV 与激活
量化 4-bit + 异常值通道 精度与内存的折中
KV 缓存 4-bit 32K 上下文从 6.3GB 降到 3.2GB
上下文封顶 32K 务实上限,不要追 64K
MTP 开启 吞吐 30%+ 提升,成本 ~2GB
配置生效 改完必须 restart model_settings.json 热改无效
角色定位 辅助/回退模型 主模型留给 64K+ 的云端方案

7. 总结

48GB Mac mini 跑本地 27B 模型,本质是在三个子系统之间做预算分配:

  1. 量化预算:权重压到 16GB,用异常值通道保住精度
  2. KV 预算:4-bit + GQA 让 32K 上下文落在 3.2GB,64K 则翻倍到 6.3GB 并触发 OOM
  3. 内存预算:统一内存不是无限池,GPU 堆受 wired 限制,OOM 与总内存余量无关

把这套参数配好、重启生效、按 provider 设上下文,本地推理就能稳定服务于编码辅助与敏感数据处理。剩下的——更大的上下文、更高的吞吐——交给更大的内存,或云端。

本地模型的意义不是替代云端,而是补上云端给不了的那一格:隐私、离线、可控。

写给未来的自己合上文章前,留下一点自己的理解。

你的私人学习便签,只存在当前浏览器,不会上传或公开。清理浏览器数据会丢失,请导出留存。三种语言共用这篇文章的便签。

接下来,走哪条路?

这篇文章暂时没有对应的已发布关联内容。 回到学习路线选择下一步 →

依据文章关联与同主题已发布内容整理,不使用随机推荐。

← 全部文章