Ollama 显存不足怎么办:24GB Mac 实测 num_ctx、并发、KV 量化各吃多少内存,以及它不会拦你的那个坑
先回答你搜的问题
- 先看
ollama ps:PROCESSOR列显示100% GPU就没有「显存不足」;显示48%/52% CPU/GPU这类,说明放不下,有一部分在 CPU 上跑。SIZE列是这个模型实际占的内存。- 最常见的原因是上下文开太大:qwen3:4b 在 4k 上下文时占 3.73GB,32k 时占 9.89GB,大头是 KV cache,不是模型本身。
- 最有效的一招:启动 Ollama 前设
OLLAMA_FLASH_ATTENTION=1和OLLAMA_KV_CACHE_TYPE=q8_0(或q4_0)。实测 32k 上下文从 9.89GB 降到 5.51GB(q8_0)/ 4.31GB(q4_0),生成速度不变。- 检查并发数:
OLLAMA_NUM_PARALLEL=4会让内存按 4 倍上下文算,但ollama ps的CONTEXT列看不出来。不需要并发就设成 1。- 别指望 Ollama 拦你:上下文设得远超内存时,它不会报「内存不够」,而是直接开始分配,系统会被拖进疯狂换页。设之前先算一下。
- Mac 上「挪到 CPU」没那么可怕:实测 qwen3:4b 全部放 CPU 只比全 GPU 慢 25%。
问题背景
本地跑大模型,「显存不足」几乎是绕不过去的一关。在 Mac 上它有两个特殊之处:
- Apple Silicon 是统一内存,没有单独的「显存」,但 Ollama 会按 Metal 报告的可用量当显存算;
- 你看到的往往不是一句报错,而是变慢、卡顿,或者整台机器开始转风扇、换页。
所以这篇不讲概念,直接在一台 24GB 的 M4 Mac mini 上量:每个设置到底吃多少内存,以及超过之后会发生什么。
问题分析
先看 Ollama 怎么理解这台机器。启动日志原文:
msg="inference compute" library=Metal name=Metal description="Apple M4" total="17.8 GiB" available="17.8 GiB"
msg="vram-based default context" total_vram="17.8 GiB" default_num_ctx=4096
24GB 内存的 Mac,Ollama 认到的显存是 17.8 GiB。按 官方文档,默认上下文按显存分档:小于 24 GiB 给 4k,24~48 GiB 给 32k,48 GiB 及以上给 256k。所以这台机器默认只有 4096。而同一份文档也写着:Tasks which require large context like web search, agents, and coding tools should be set to at least 64000 tokens.
于是大家会去调大上下文,「显存不足」就从这里开始。文档里和内存直接相关的几条:
- 内存需求按
OLLAMA_NUM_PARALLEL×OLLAMA_CONTEXT_LENGTH计算(FAQ) - KV cache 可以量化:
q8_0约为f16的 1/2,q4_0约为 1/4,前提是开 Flash Attention ollama ps的PROCESSOR列:100% GPU/100% CPU/48%/52% CPU/GPU
技术方案与选型
| 方案 | 结论 | 理由 |
|---|---|---|
| 单独起一个 Ollama 实例逐项测 | ✅ 采用 | 在 127.0.0.1:11500 起独立实例,只读复用本机已下载的模型,每测一项就卸载,/api/ps 读占用、/api/generate 读速度 |
| 用 27B 模型直接撞内存上限 | ❌ 放弃 | 实验时这台机器上还跑着另一个占 7GB 的回测任务,27B(约 17GB)会把它挤进换页 |
| Docker 里跑 | ❌ 排除 | 官方 FAQ:GPU acceleration is not available for Docker Desktop in macOS,测出来全是 CPU 数据 |
几项保护措施:
- 加
OLLAMA_NOPRUNE=1:ollama serve默认启动时会清理它认为没用的模型文件,实验实例不该动你的模型库; - 内存守护脚本:每 2 秒查一次系统可用内存,低于 35% 立即 kill 实验用的 Ollama(后面真触发了一次);
- 模型用
qwen3:4b(Q4_K_M,模型文件 2.50GB),每次固定生成 128 个 token,temperature 0。
实测过程
1. 上下文长度:大头是 KV cache
默认设置(f16 KV、不开 Flash Attention):
num_ctx |
ollama ps 占用 |
PROCESSOR | 生成速度 |
|---|---|---|---|
| 4096(默认) | 3.73 GB | 100% GPU | 35.3 / 35.5 tok/s |
| 8192 | 4.61 GB | 100% GPU | 35.5 tok/s |
| 16384 | 6.37 GB | 100% GPU | 35.4 tok/s |
| 32768 | 9.89 GB | 100% GPU | 33.8 tok/s |
模型文件只有 2.50GB,32k 上下文时却占了 9.89GB。粗算下来,qwen3:4b 每多 1k 上下文大约多占 0.21GB(不同模型差别很大,取决于层数和 KV 头数)。
32k 时系统可用内存最低降到 37%,已经接近守护阈值,所以没有继续用 f16 测 64k(按斜率约 16GB)。
2. Flash Attention + KV 量化:同样 32k,占用降一半以上
| 设置(32k 上下文) | 占用 | 生成速度 |
|---|---|---|
| 默认(f16,不开 FA) | 9.89 GB | 33.8 tok/s |
OLLAMA_FLASH_ATTENTION=1 |
7.78 GB | 36.3 tok/s |
FA + OLLAMA_KV_CACHE_TYPE=q8_0 |
5.51 GB | 35.4 tok/s |
FA + OLLAMA_KV_CACHE_TYPE=q4_0 |
4.31 GB | 33.6 / 33.5 tok/s |
FA + q4_0,64k 上下文 |
5.77 GB | 36.8 / 36.6 tok/s |
**开 q4_0 之后,64k 上下文的占用比默认设置下的 32k 还少。**生成速度在 33.5~36.8 tok/s 之间,没有因为量化变慢。服务日志能看到设置生效了:FlashAttention:Enabled KvSize:32768 KvCacheType:q4_0。
质量方面,文档说 q8_0 usually has no noticeable impact,q4_0 在长上下文下的损失 may be more noticeable,并且 GQA 比例高的模型(文档举的例子是 Qwen2)受影响更大。本文只测了内存和速度,没测回答质量,所以建议先用 q8_0。
3. 并发数:看不见的 4 倍
| 设置 | ollama ps 的 CONTEXT |
占用 |
|---|---|---|
OLLAMA_NUM_PARALLEL=1,num_ctx=8192 |
8192 | 4.61 GB |
OLLAMA_NUM_PARALLEL=4,num_ctx=8192 |
8192 | 9.89 GB |
并发 4 路、每路 8k,占用和单路 32k 一字不差(9.89GB),但 ollama ps 的 CONTEXT 列仍然显示 8192。如果你在 Open WebUI、多个编辑器插件之间共用一个 Ollama,又把并发调高了,内存就是这样悄悄翻倍的。
4. 上下文开到远超内存:Ollama 不会拦你
用 num_ctx=262144 请求 qwen3:4b(f16 KV)。原本预期 Ollama 会先估算、发现放不下,然后报错。实际的服务日志:
load request="{Operation:fit … KvSize:262144 … GPULayers:1[ID:0 Layers:1(35..35)] …}"
load request="{Operation:fit … KvSize:262144 … GPULayers:[] …}"
load request="{Operation:alloc … KvSize:262144 … GPULayers:[] …}"
load request="{Operation:alloc … KvSize:262144 … GPULayers:15[ID:0 Layers:15(21..35)] …}"
…
msg="total memory" size="54.6 GiB"
它估算出总共需要 54.6 GiB,比这台机器的 24GB 物理内存多一倍多,但照样进入了分配阶段(Operation:alloc):先试全部放 CPU,再试 15 层放 GPU。16 秒内,系统可用内存从 75% 掉到 23%,守护脚本触发,把实验用的 Ollama kill 掉了。之后服务日志记录 Load failed … error="model failed to load, this may be due to resource limitations or an internal error, check ollama server logs for details"。这句报错是 kill 之后出现的,如果不 kill,它最终会怎样,本文没有测(继续测会拖垮这台机器上的其他服务)。
这就是「显存不足」最危险的形态:不是一句清楚的报错,而是整台机器被拖进换页。设置大上下文之前,先按上面的斜率估一下。
5. 部分挪到 CPU:在 Mac 上没那么慢
用 num_gpu 控制放在 GPU 上的层数(qwen3:4b 共 37 层,4k 上下文):
| GPU 层数 | ollama ps 显存占比 |
生成速度 |
|---|---|---|
| 37(全部) | 100% | 35.4 / 35.5 tok/s |
| 27 | 60% | 30.9 tok/s |
| 18 | 42% | 27.3 tok/s |
| 9 | 25% | 26.3 tok/s |
| 0(全 CPU) | 0% | 26.6 / 26.3 tok/s |
**全部放 CPU 只比全 GPU 慢约 25%。**这和独立显卡的经验不一样:Apple Silicon 的 CPU 和 GPU 共用同一块内存,层放在哪一边都不需要经过 PCIe 在显存和内存之间搬数据(为什么差距只有 25%,本文没有进一步拆解)。所以在 Mac 上看到 CPU/GPU 混合并不意味着「完蛋了」,真正要防的是第 4 节那种总量超过物理内存的情况。(这是 4B 小模型的数据,大模型、长 prompt 的预填充阶段差距可能更大,本文没测。)
实践效果
1. 先诊断:
ollama ps
看 SIZE(实际占用)、PROCESSOR(有没有挪到 CPU)、CONTEXT(单路上下文,注意不含并发倍数)。
2. 大多数情况,加两个环境变量就够了(macOS 上用 launchctl setenv 或在启动 ollama serve 前 export,改完要重启 Ollama):
export OLLAMA_FLASH_ATTENTION=1
export OLLAMA_KV_CACHE_TYPE=q8_0 # 内存特别紧时换 q4_0,代价是长上下文下可能掉质量
export OLLAMA_NUM_PARALLEL=1 # 不需要并发就别开
**3. 上下文按需开,不要一步到顶。**接编程工具确实需要 64k 左右,24GB 的 Mac 上跑 4B 模型,配合 q4_0 实测 5.77GB,完全放得下;7B、14B 就要按比例重新估算。可以用站内的 显存计算器 先算,或者直接看 Mac mini M4 24GB 能跑哪些模型。
4. 设了大上下文之后,留意内存压力(活动监视器的「内存压力」图,或 memory_pressure 命令)。Ollama 不会提前拦你,一旦变红,马上 ollama stop <模型名>。
**5. Mac 用户不必把「挪到 CPU」当成灾难。**小模型全 CPU 也只慢 25% 左右;真正要避免的是总量超过物理内存。
踩坑
- **以为 Ollama 会先估算再拒绝。**实验设计时把「超大上下文」当成安全的测试项,认为它只会在估算阶段报错。实际上它直接进入了分配阶段,靠内存守护才及时停下。教训:在共享机器上做内存实验,守护脚本必须先于实验启动。
- **
ollama serve启动时会清理模型文件。**日志里有一行total unused blobs removed: 0,这次没删东西,但实验实例改用OLLAMA_NOPRUNE=1启动,避免误删本机模型库。 - **prompt 处理速度不能用。**测试提示只有二十来个 token,同样的设置两次跑出 120 和 248 tok/s,这个指标在本文中不引用。
未验证清单:27B 等大模型在 24GB 机器上的分层情况(实验时机器上有其他高内存任务,没有加载);KV 量化对回答质量的影响;超大上下文不被 kill 时 Ollama 最终的结局;长 prompt 预填充阶段 CPU/GPU 的速度差;除 qwen3:4b 以外模型的「每 1k 上下文占用」斜率。除标注两个数值的几组外,其余设置各只跑了 1 次。