Ollama 0.4 服务在请求到达后才开始加载模型(懒加载),RTX 3090 / 24 GB 显存下加载 Llama-3-8B Q4_K_M 需要 15-25 秒,之后同样的请求只需要 0.3 秒(Time to First Token,TTFT)。用户体验上,第一次问问题需要等半分钟才有响应,而后续对话流畅。这是典型的冷启动问题:模型文件从磁盘读入显存的过程决定了首 token 延迟。
常见原因
1. Ollama 的模型懒加载 + 自动 unload 策略
Ollama 默认在收到请求时才加载模型,并在 5 分钟(可配置)无请求后自动卸载模型释放显存。下一次请求到来时需要重新加载,触发冷启动。对于 70B 模型,冷启动可能长达 60-90 秒。
怎么判断:请求间隔超过 5 分钟后再次请求,TTFT 是否明显变长;ollama ps 查看模型是否还在内存中(若无输出说明已卸载)。
2. 模型文件在 HDD 而非 SSD,读取速度成为瓶颈
从 HDD 读取 40 GB 的 Q4_K_M 文件(顺序读取约 150 MB/s),需要约 267 秒;NVMe SSD(顺序读取 3000-5000 MB/s)只需要 8-13 秒。若模型存储在机械硬盘上,冷启动时间可能长达几分钟。
怎么判断:df -h ~/.ollama/models 确认所在分区;hdparm -Tt <device> 或 dd if=/dev/zero of=/tmp/test bs=1M count=1000 oflag=direct 测试磁盘顺序读写速度。
3. CUDA 初始化需要编译 JIT kernel
NVIDIA GPU 在某些驱动版本下,首次运行特定 CUDA kernel 时会触发 JIT(Just-In-Time)编译,时间可能长达 10-30 秒。这发生在 GPU 计算开始前,表现为模型文件已加载但 TTFT 仍然很长。
怎么判断:nvidia-smi 观察加载阶段 GPU 利用率是否有一段时间保持在 10-30%(JIT 编译阶段),而非 0%(等待)或 90%以上(推理阶段)。
4. mmap 懒加载导致首批 token 时有大量缺页中断
llama.cpp 默认用 mmap 加载模型文件,启动很快,但实际权重数据直到被访问时才从磁盘读取。首次推理时大量缺页中断(page fault)会导致前几个 token 的生成速度极慢,后续 token 才恢复正常速度。
怎么判断:perf stat -e page-faults ./llama-cli -m model.gguf -p "hello" -n 10 2>&1 | grep page-fault;若 page-fault 数量超过 10 万次,就是缺页中断问题。
5. Apple Silicon 上 Metal shader 首次编译
macOS Metal 后端在首次运行时需要编译 Metal shader,在 M2/M3 上可能需要 5-15 秒。编译完成后结果会被缓存(~/Library/Caches/),后续启动不再重复编译,但清除缓存或更新 Ollama 版本后需要重新编译。
怎么判断:查看 Ollama 日志中是否有 compiling shader 相关信息;在 macOS Activity Monitor 中观察首次加载时 GPU 是否有一段高 CPU 占用(shader 编译在 CPU 上进行)。
6. 多模型竞争显存导致频繁 swap
若 Ollama 配置了同时保持多个模型在显存中(OLLAMA_MAX_LOADED_MODELS=2),模型间频繁换入换出,每次都触发冷启动延迟。
怎么判断:ollama ps 查看当前加载的模型列表;若有 2 个以上模型在内存中,检查显存是否不够。
最短修复路径
Step 1:延长 Ollama 的模型保活时间
# 方法 1:设置环境变量(在服务启动前设置)
export OLLAMA_KEEP_ALIVE=30m # 保持 30 分钟,默认 5 分钟
# 设置为 -1 表示永不卸载
export OLLAMA_KEEP_ALIVE=-1
# macOS Launch Agent 配置
# 编辑 ~/Library/LaunchAgents/com.ollama.ollama.plist
# 在 EnvironmentVariables 节点下添加 OLLAMA_KEEP_ALIVE
# 方法 2:在每次 API 请求中指定 keep_alive
curl http://localhost:11434/api/generate -d '{
"model": "llama3:8b",
"prompt": "hello",
"keep_alive": "30m"
}'
Step 2:预加载模型(发送空请求)
# 在服务启动后立即发送一个预热请求,让模型提前加载
# 可以加到服务启动脚本的最后
curl -s http://localhost:11434/api/generate -d '{
"model": "llama3:8b",
"prompt": "",
"keep_alive": "1h"
}' > /dev/null
echo "模型预热完成"
Step 3:使用 —mlock 锁定模型到内存
# llama.cpp 启动时锁定内存,防止 swap 和被 OS 回收
./llama-server \
-m model-Q4_K_M.gguf \
--mlock \
-ngl 35 \
--ctx-size 4096 \
--host 0.0.0.0 \
--port 8080
# 验证 mlock 效果
# 1. 加载完成后 nvidia-smi 查看显存已满
# 2. 长时间不用后 ollama ps 仍显示模型在内存中
Step 4:将模型文件迁移到 NVMe SSD
# 查看当前 ollama 模型目录
ls -lh ~/.ollama/models/blobs/ | head -5
# 若在 HDD 上,迁移到 SSD
# 方法:使用 OLLAMA_MODELS 环境变量指向 SSD 路径
export OLLAMA_MODELS=/path/to/ssd/ollama_models
# 将现有模型拷贝过去
rsync -avh ~/.ollama/models/ /path/to/ssd/ollama_models/
# 验证加载速度(计时)
time ollama run llama3:8b "hello" --verbose 2>&1 | grep "load duration"
Step 5:用 --numa 和 --no-mmap 消除缺页中断
# 对于首 token 延迟敏感的场景,启动时完全预读模型到内存
./llama-server \
-m model-Q4_K_M.gguf \
--no-mmap \
--mlock \
-ngl 35 \
--ctx-size 4096
# --no-mmap:启动时完整读取模型(启动慢但推理快)
# --mlock:锁定内存防止换出
预防建议
- 将常用模型的保活时间设为至少 30 分钟,覆盖典型用户对话的间隔。
- 应用启动时发送一个预热请求,避免第一个真实用户遇到冷启动。
- 将模型文件存储在 NVMe SSD 上,机械硬盘的加载速度会让冷启动不可接受。
- 对于 7B 以下模型,使用 Q8_0 量化提升推理质量;对于 70B 模型,使用 Q4_K_M 控制加载时间。
- 在生产环境中监控 TTFT(Time to First Token)指标,设置告警阈值(如超过 5 秒),及时发现模型被卸载导致的冷启动。
- 若有多个模型需要热加载,配置足够大的
OLLAMA_MAX_LOADED_MODELS同时保持多个模型在显存中,避免频繁换入换出。
常见问答 (FAQ)
Q: ollama run 的 --verbose 参数里的 “load duration” 是什么意思?
A: “load duration” 是模型从磁盘加载到 GPU 显存的时间。它与 TTFT(首 token 时间)的差值就是 prompt 评估时间(prompt eval duration)。load duration 大说明冷启动慢;prompt eval duration 大说明 prompt 太长。
Q: 能不能用 Docker 持久化模型加载状态,跨容器重用?
A: 不能直接持久化 GPU 显存状态。但可以用 --keep-alive -1 + Docker restart policy 让容器和 Ollama 服务保持长期运行,模型不被卸载,效果相当于持久化加载。
Q: 为什么 LM Studio 的冷启动比 Ollama 快?
A: LM Studio 默认不会自动卸载模型,加载一次后保持在显存中直到手动卸载。Ollama 的懒加载和自动卸载策略更适合服务器场景(多用户共享),但对单用户场景会带来冷启动问题。设置 OLLAMA_KEEP_ALIVE=-1 可以让 Ollama 的行为接近 LM Studio。
Q: 首次请求后的第二次请求也慢,只有第三次才正常,为什么? A: 可能是 KV cache 预热问题。首次请求评估 prompt 时填充了 KV cache,若 KV cache 还在”冷”状态(GPU L2 cache 未命中),第二次请求可能也偏慢。第三次请求时 GPU cache 已经热了,延迟才完全正常。这种情况通常在 KV cache 较大时更明显。
相关阅读
- Ollama 探测不到 GPU,全跑在 CPU
- LM Studio 加载模型时显存 OOM
- 多 GPU 没分配上,模型只跑在卡 0
- llama.cpp 在网络盘上 mmap 失败
- 本地 RAG 索引重建慢到无法忍受
标签: #local-llm #ollama #排查