一句话结论:Ollama 释放显存不用重启服务,请求里传 keep_alive 0 就会卸载;代价是下一次请求要重新加载权重,本机实测多花 0.81 到 3.2 秒。批量跑 100 条任务时这样设,等于白送一分半钟给加载。想常驻就传 -1,但记得跑完手动 stop,否则它会一直挂在内存里。
一、三条命令:卸载、常驻、查状态
先把能直接粘贴的三条放前面。全部针对 11434 端口的本地服务,桌面版和 CLI 共用这一个端口。
# 立刻卸载,释放显存与内存
curl -s http://127.0.0.1:11434/api/generate -d '{"model":"qwen3:8b","keep_alive":0}'
# 常驻 30 分钟(每次请求都会把倒计时续到 30 分钟)
curl -s http://127.0.0.1:11434/api/generate -d '{"model":"qwen3:8b","keep_alive":"30m"}'
# 看当前谁在内存里、还剩多久
ollama ps
curl -s http://127.0.0.1:11434/api/ps
这三条不需要真跑推理。prompt 传一个字符就够,实测 0.16 秒返回,因为服务只做加载与卸载,不生成 token。ollama ps 的 UNTIL 列会直接告诉你还剩多久,API 版本看 expires_at 字段,精度到纳秒。
二、四种写法分别会发生什么
keep_alive 接受 Go 的时长格式(30m、1h、90s)和纯数字(单位秒)。正数代表驻留时长,0 代表立刻卸载,负数代表永久驻留。行为差异比文档写的细,下表是 0.40.14 上逐条跑出来的结果。
| 写法 | 服务端行为 | 实测结果 |
|---|---|---|
| 不传 | 用环境变量 OLLAMA_KEEP_ALIVE,未设则 5 分钟 | 新加载时 expires_at 为当前时间加 5 分钟 |
| 0 或 “0s” | 响应结束后卸载 | 0.2 秒内 /api/ps 清空,llama-server 进程退出,RSS 归零 |
| “90s” 或 90 | 90 秒内保持,每次请求重置 | 窗口内再发请求,expires_at 跟着后移 3 秒 |
| -1 或 “-1m” | 永久驻留 | expires_at 显示 2319 年,ollama ps 的 UNTIL 列写 Forever |
-1 对应的 2319 年不是玩笑,那是服务端用一个超大时长算出来的绝对值。看到这个年份就说明模型不会自己走,只能靠 stop 或者显式改短。
三、换出一次到底花多少时间
测试机是 M4 16GB 统一内存,Ollama 0.40.14,模型 qwen3:8b(5.2GB),上下文 4096。同一台机器上跑,只改一个变量,记录墙钟耗时与响应里的 load_duration。
| 场景 | 墙钟耗时 | load_duration |
|---|---|---|
| 服务刚起来,第一次加载 | 4.28 秒 | 3.86 秒 |
| 卸载后立刻重载 | 2.74 秒 | 2.56 秒 |
| 权重已被系统缓存,再重载 | 0.93 秒 | 0.81 秒 |
| 模型常驻,直接出结果 | 0.23 秒 | 0.01 秒 |
| keep_alive 0 连发三次 | 0.16 / 0.99 / 0.99 秒 | 0 / 0.81 / 0.81 秒 |
最后一行值得多看两眼。第一次 0.16 秒是因为模型当时还热着,复用之后顺手卸载;后两次各自完整走了一遍加载,每次 0.8 秒。也就是说 keep_alive 0 的语义是每次请求结束都拆掉,下一条请求从零开始。
短请求的差距最刺眼。同样一句两个字的提示,常驻状态 0.23 秒,卸载状态 0.99 秒,慢了 4 倍多。如果你在跑批处理,100 条就是 100 次加载,按 0.81 秒算白丢 81 秒,冷缓存时按 3.86 秒算就是 386 秒。
四、设了 5 分钟,为什么半小时还在
因为倒计时是滑动的,每次请求都从零开始算。实测把窗口设成 90 秒,等 3 秒再发一次请求,expires_at 从 21:40:04 变成 21:40:07,正好后移了 3 秒。只要你请求的间隔小于窗口长度,模型永远到不了过期时间。
还有一个更常见的误会:用 ollama run 聊完,输入 /bye 退出,以为模型释放了。实测退出后 ollama ps 仍显示 qwen3:8b,UNTIL 列写 4 minutes from now。终端退出的只是客户端,服务端按 5 分钟窗口继续养着它。要立刻放掉就 ollama stop qwen3:8b,实测 0.09 秒完成,进程随即消失。
五、-1 之后改不回来,这是最容易踩的一条
显式传过一次 keep_alive,后续不带这个字段的请求会沿用上一次的窗口,不会回落到默认 5 分钟。实测先发 keep_alive -1,再连发两次不带 keep_alive 的请求,expires_at 一直停在 2319 年,只是跟着请求时间往后挪了两秒。
想改回来必须显式覆盖。发一次 keep_alive 传 5m,expires_at 立刻变成当前时间加 5 分钟。这条在排查时特别有用:同事抱怨机器被模型占满,你去 ollama ps 看到 Forever,就知道有人曾经发过 -1,而且后面所有请求都在默默继承这个设定。
服务重启才会复位成 5 分钟。这是唯一不靠显式传参的复位方式。
六、两个模型互相踢:16GB 机器只能留一个
Ollama 启动日志里有一行 vram-based default context,后面写着 total_vram=11.8 GiB。这就是它认为自己能用的显存上限,不是机器标称的 16GB。两个 5GB 级模型塞不进 11.8GB。
实测先常驻 qwen3:8b 和一个 1.2GB 的 embedding 模型,再加载 qwen3.5:9b,前两个当场被踢掉,/api/ps 只剩 9b。窗口长短救不了,因为把模型挤走的是容量,跟时间窗口没有关系。
| 动作 | 墙钟耗时 | 换出结果 |
|---|---|---|
| 加载 qwen3:8b | 2.77 秒 | 8b 在位 |
| 切到 qwen3.5:9b | 4.03 秒 | 8b 被踢 |
| 切回 qwen3:8b | 3.25 秒 | 9b 被踢 |
| 再切到 qwen3.5:9b | 3.80 秒 | 8b 被踢 |
来回切四次,没有一次命中缓存,每次都在重新加载。这种场景下 keep_alive 已经没有意义,因为模型是被容量挤走的。要真让两个模型共存,只有三条路:换更小的量化、把 OLLAMA_MAX_LOADED_MODELS 设成 2 并且总占用不超上限、或者干脆开两个进程分机器跑。
补充一条对比:设 OLLAMA_MAX_LOADED_MODELS=2 之后,5.28GB 的 8b 和 4.68GB 的 embedding 确实同时在位,合计 9.96GB,没超 11.8GB。上限是容量与这个变量共同决定的,谁先到谁说话。
七、OpenAI 兼容端点认 keep_alive,但不认 num_ctx
很多人以为 /v1/chat/completions 只实现了一半参数,所以 keep_alive 也无效。实测是有效的:带 keep_alive 0 请求之后 /api/ps 立刻清空,不带则新加载给 5 分钟窗口。两个端点在这件事上表现一致。
但上下文长度是反例。站内另一篇实测过,num_ctx 走 /v1 端点时传顶层或塞进 options 都返回 200,实际 context_length 恒为 4096。所以在这套接口上,能改的是驻留时长,改不了上下文。
用 Python 的 openai SDK 时,keep_alive 不是标准字段,得走 extra_body:
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:11434/v1", api_key="ollama")
r = client.chat.completions.create(
model="qwen3:8b",
messages=[{"role": "user", "content": "hi"}],
extra_body={"keep_alive": "30m"},
)
八、三个失败现场与判断方法
第一个是参数写错。传 keep_alive abc 会直接回 400,错误体是 time: invalid duration,值必须写成 30m、90s 这类 Go 时长,或者纯数字秒。中文单位写 30分钟 同样报这个错。判断方法只有一条:看 400 的 error 字段,出现 time: invalid duration 就是格式问题,与服务无关。
第二个是内存被别的东西占住,请求静默返回空结果。测试时开第二个 Ollama 实例占掉 9.9GB,再请求 32768 上下文,HTTP 状态码照样 200,响应体是 model 为空串、done 为 false、response 为空。客户端只判状态码就会把空字符串当成正常输出,然后在下游报一个跟模型毫无关系的错。判断方法:检查响应里的 model 字段,空串就是没加载起来。
第三个是卸载了但内存看着没回来。llama-server 进程确实退了,ps 查 RSS 是 0,可系统内存占用掉得慢。权重文件留在系统页缓存里,这属于正常的缓存行为,内存并没有漏。反过来这正是第二次加载只要 0.81 秒的原因,冷启动要 3.86 秒。判断方法:ps -Ao rss,comm 查不到 llama-server 就说明已经释放,别再去重启服务。
九、按场景抄这三段配置
离线批处理最怕每条都重载。把 keep_alive 放进每次请求,跑完统一 stop,比在客户端 sleep 更省事:
# 批处理:先常驻,跑完再卸
curl -s http://127.0.0.1:11434/api/generate -d '{"model":"qwen3:8b","keep_alive":"-1"}'
# ... 循环发你的任务 ...
ollama stop qwen3:8b
交互式开发给 30 分钟就够。上午写代码时模型一直热着,午休回来重新加载一次,代价 0.8 到 3 秒,可接受。给 -1 然后忘记 stop,是这台机器后来变卡的常见起点。
一次性脚本或者 CI 里,keep_alive 0 反而更干净,容器退出前不会留驻留进程。但要知道循环 100 次会多付 81 到 386 秒的加载时间,能合并成一条长请求就别拆成一百条短请求。
要在服务端统一改默认值,用环境变量启动,实测生效:
OLLAMA_KEEP_ALIVE=15s OLLAMA_MAX_LOADED_MODELS=2 ollama serve
这样起来之后,不带 keep_alive 的请求会在 15 秒后卸载,实测 expires_at 正好是当前时间加 15 秒。注意这个变量只在服务启动时读一次,改完必须重启才生效,而且它管不到已经加载的模型。
十、常见问题
问:keep_alive 0 会损坏模型文件吗?
不会。卸载只是结束推理进程、释放内存,权重文件是只读的,下一次加载重新 mmap 一遍。模型文件 5225374496 字节,反复卸载加载后大小与修改时间都没有变化。
问:ollama ps 显示 Forever,怎么恢复默认?
显式发一次带 keep_alive 5m 的请求,或者 ollama stop 直接停掉。只关终端窗口没有用,服务端会继续按 5 分钟窗口养着模型。
问:已经设了 0,为什么连着两条请求还是慢?
因为每条请求结束都卸载,下一条从零加载。实测第一次 0.16 秒是复用了已热模型,后面两次各 0.99 秒。批处理要用 -1 常驻,别用 0。
延伸阅读
- DeepSeek Harness 密钥放哪:四层解析与五个报错2 天前
- GPT-OSS开源权重模型发布:120B/20B双版本Apache 2.0可商用,128K上下文与本地部署全解析2 月前
- Qwen-Image 与 FLUX 显存对比:22GB 卡跑 1024 实测3 天前
- 讯飞星火 X2.6:293B 国产算力训练,4B 端侧 BFCL 拿 71.8 分3 周前
继续读
同一批更新里这几篇值得一看:
- 本地大模型显存怎么算:8B 每 1K 上下文 144MB2026-11-09
- Ollama 上下文怎么设:4096 实测只吃 2050 token2026-11-09
- NVIDIA Dory 1.0 4B 怎么跑:3.7GB GGUF 四档算力实测2026-11-09
想快速查定义?对应的词条在这里:
- Llama词条 · AI 自学笔记
