Ollama 实战排障:为什么明明装了显卡,模型却跑在 CPU 上?一套完整可复现的 GPU 排查方法

0 阅读12分钟

最近在一台 CentOS 8 服务器上部署 Ollama,本来目标很简单:
qwen3.5:9b 拉下来,跑在 NVIDIA RTX 3060 上。

结果实际情况是:

  • nvidia-smi 正常
  • 显卡驱动正常
  • Ollama 服务也启动了
  • 模型也成功拉下来了
  • ollama ps 显示的却是:
PROCESSOR   100% CPU

也就是说,模型根本没有跑在 GPU 上,而是完全跑在 CPU 上

这类问题很容易把人带偏。很多人一看到“没跑 GPU”,第一反应就是:

  • 是不是显卡不支持?
  • 是不是 CUDA 没装?
  • 是不是模型太大?
  • 是不是 Ollama 有 bug?

但真正把这个问题解决下来后,我发现这类排障最关键的并不是“会不会几条命令”,而是有没有正确的排查路径

这篇文章就把这次完整过程整理出来。 我会尽量不用“只给答案”的方式,而是把背后的判断思路讲出来。 这正是我理解的 Polanyi 默会知识(Tacit Knowledge):很多时候,真正决定排障成败的,不是某一条标准命令,而是长期实践中形成的“先看哪里、哪些现象最关键、如何缩小范围”的经验判断。


一、问题现象:模型能跑,但跑在 CPU 上

一开始,Ollama 其实已经装上了,模型也拉下来了:

ollama run qwen3.5:9b

看起来一切正常,模型能回答问题,命令也没有报错。

但是一执行:

ollama ps

看到的是:

NAME        ID              SIZE    PROCESSOR   CONTEXT   UNTIL
qwen3.5:9b  xxxxxxxxxxxx    8.5 GB  100% CPU    4096      4 minutes from now

这个信息非常关键。

很多人会忽略 ollama ps,但在我看来,它是判断 Ollama 是否真正跑在 GPU 上的第一现场。

如果这里显示:

  • 100% GPU:说明模型完整跑在 GPU 上
  • 100% CPU:说明完全跑在 CPU 上
  • xx% CPU / xx% GPU:说明部分层卸载到了 GPU

所以第一条经验是:

不要凭感觉判断 Ollama 是否使用了 GPU,先看 ollama ps


二、第一层排查:先分清“显卡不可用”还是“Ollama 没用上显卡”

这个问题最容易一上来就陷入“重装 CUDA、重装驱动、重装系统”的大坑。

但我当时先做了一个更基本的判断:

1. 先看显卡驱动是不是正常

nvidia-smi

我的机器返回正常,能看到两张 RTX 3060,类似这样:

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 570.xxx.xx    Driver Version: 570.xxx.xx    CUDA Version: 12.8   |
| GPU  Name                ...
| 0    NVIDIA GeForce RTX 3060
| 1    NVIDIA GeForce RTX 3060
+-----------------------------------------------------------------------------+

这一步说明的不是“Ollama 一定能用 GPU”,而是先确认:

  • Linux 内核已经识别显卡
  • NVIDIA 驱动已经正常安装
  • 用户态基本可以看到 GPU

这只能说明“显卡没坏、驱动没坏”,不能说明“Ollama 一定能调用 GPU”。

这就是排障里一个很重要的 tacit knowledge:

系统看到 GPU,不等于应用一定能用 GPU。


三、第二层排查:看 Ollama 服务是否真的启动正常

当时我先确认了 Ollama 客户端:

which ollama
ollama -v

返回类似:

/usr/local/bin/ollama
client version is 0.18.2

说明客户端已经安装成功。

但随后发现:

systemctl start ollama

报错:

Unit ollama.service not found

这说明安装脚本虽然把 ollama 命令装好了,但 systemd 服务文件没有正确生成

于是我手工创建了 /etc/systemd/system/ollama.service

[Unit]
Description=Ollama Service
After=network-online.target

[Service]
ExecStart=/usr/local/bin/ollama serve
User=ollama
Group=ollama
Restart=always
RestartSec=3
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Environment="OLLAMA_MODELS=/home/ollama/models"

[Install]
WantedBy=multi-user.target

然后启动:

systemctl daemon-reload
systemctl enable --now ollama
systemctl status ollama --no-pager

这一步的意义是:

  • 确保 Ollama 服务真正稳定运行
  • 排除“只是命令行临时启动,服务环境和交互环境不一致”的问题
  • 为后续看日志做准备

这也是一个排障经验:

GPU 问题不要只盯着模型命令本身,要把服务启动方式也纳入排查。

因为很多时候,命令行前台运行和 systemd 后台运行,环境变量、权限、库路径都可能不同。


四、第三层排查:用日志判断 Ollama 处于哪个阶段失败

当模型仍然跑在 CPU 上时,我没有继续反复 run,而是开始看日志:

journalctl -u ollama -n 200 --no-pager | egrep -i 'cuda|gpu|runner|library|offload|vram'

日志里出现了非常关键的几类信息:

1. 看到了 GPU 发现阶段日志

discovering available GPUs...

这说明 Ollama 至少进入了 GPU 探测阶段。

2. 但推理后端仍然是 CPU

inference compute id=cpu library=cpu

这一句几乎就是“定性证据”。

它说明在真正选择推理后端的时候,Ollama 并没有进入 CUDA 路径,而是选了 CPU library。

3. 模型层没有卸载到 GPU

GPULayers:[]
offloading 0 repeating layers to GPU
offloaded 0/33 layers to GPU
runner.vram="0 B"

这几句连起来看,结论就很明确了:

  • Ollama 尝试进入模型加载阶段了
  • 但一个层都没放到 GPU
  • 最终 VRAM 视角下使用量是 0

这个阶段我形成了一个很重要的判断:

问题不是“模型太大放不下”,而是“推理后端根本没进 CUDA”。

为什么这么说?

因为如果只是“模型太大”,通常更容易看到的是“部分层上 GPU,部分层在 CPU”,而不是 0 层上 GPU

这就是排障中非常重要的一个经验判断:

先区分“GPU 不够用”和“GPU 根本没被用上”,这两类问题解决思路完全不同。


五、第四层排查:环境变量会不会把 GPU 路径带偏

最开始我为了让 Ollama 使用两张显卡,在服务里加过:

Environment="CUDA_VISIBLE_DEVICES=0,1"

看起来很合理,但日志里反而出现了:

user overrode visible devices
if GPUs are not correctly discovered, unset and try again

这句话让我意识到:

  • 这个变量不是一定有益
  • 在某些环境里,它反而可能干扰自动探测

于是我先做了一个“减法”动作: CUDA_VISIBLE_DEVICES 去掉,让 Ollama 自己探测。

这一步的结果很有价值:

  • 去掉后,日志里不再出现 CUDA_VISIBLE_DEVICES=0,1
  • 但仍然显示 library=cpu
  • 仍然 offloaded 0/33 layers to GPU

这说明问题已经被进一步缩小:

不是环境变量覆盖导致的,而是更底层的问题。

这就是默会知识在排障里的作用。 不是每一步都能直接解决问题,但每一步都要能“缩小问题范围”。


六、第五层排查:权限是不是问题

接下来我查了服务用户和设备权限:

id ollama
ls -l /dev/nvidia*

看到:

  • ollama 用户已经在 videorender 组里
  • /dev/nvidia0/dev/nvidia1/dev/nvidiactl/dev/nvidia-uvm 都存在
  • 权限看起来也正常

这一步让我排除了另一个常见误区:

不是所有 GPU 调用失败都是权限问题。

很多时候一看到 GPU 不工作,大家就习惯性去改 chmod 777 /dev/nvidia*,甚至改一堆组权限。 但这次的现象更像是:

  • 设备文件没问题
  • 显卡驱动没问题
  • Ollama 也能发现“有 GPU 这回事”
  • 只是最后没有找到自己的 GPU 运行库

这一步再次缩小了范围。


七、第六层排查:真正的根因,不在显卡,而在 Ollama 自己的运行库目录

后面我查了两个目录:

ls -lah /usr/local/lib/ollama
find /usr/local/lib/ollama -type f

结果发现:

/usr/local/lib/ollama

居然是空目录。

而日志里明明显示 Ollama 启动时使用的是:

LD_LIBRARY_PATH=/usr/local/lib/ollama
OLLAMA_LIBRARY_PATH=/usr/local/lib/ollama

也就是说:

  • Ollama 运行时去 /usr/local/lib/ollama 找自己的运行库
  • 但这个目录是空的
  • 所以它根本加载不到 CUDA 后端
  • 最后自然只能回退到 CPU

这一下就把整件事解释通了。

这是这次排障里最核心的结论:

GPU 运行失败,不一定是显卡、驱动、CUDA 的问题,也可能是 Ollama 自己的运行库没有装完整。

随后我又查了另一个目录:

ls -lah /usr/lib/ollama
find /usr/lib/ollama -type f | egrep -i 'cuda|ggml|runner|llama'

结果发现 /usr/lib/ollama 里其实是有内容的,里面有 CUDA 相关运行库。

也就是说,这次安装属于一种“半成功状态”:

  • 可执行文件装到了 /usr/local/bin/ollama
  • 但运行库没有同步到 /usr/local/lib/ollama
  • 结果程序能启动,模型也能拉
  • 但 GPU 后端不可用

这类问题特别容易误导人,因为它不是“装不上”,而是“能用,但用得不对”。


八、最终修复:把正确的运行库补到 Ollama 实际查找的目录

既然 /usr/lib/ollama 里有完整内容,而 /usr/local/lib/ollama 是空的,那最快的修法就是把两者接起来。

我最后采用的是这种方式:

systemctl stop ollama
rm -rf /usr/local/lib/ollama
ln -s /usr/lib/ollama /usr/local/lib/ollama
ldconfig
systemctl start ollama

这么做的好处是:

  • 不需要重新拉模型
  • 不需要重装驱动
  • 不需要重装系统
  • 直接把运行库路径纠正过来

修复后再次执行:

ollama run qwen3.5:9b
ollama ps

结果就变成了:

PROCESSOR   100% GPU

同时 nvidia-smi 也能看到显存和 GPU 利用率变化。

到这里,问题彻底解决。


九、这次排障里最重要的几个“默会知识”

我最后把这次的经验总结成几条,供以后快速判断。

1. 不要先重装,先定性

先回答一个问题:

到底是“GPU 根本没识别”,还是“GPU 识别了但没被用上”?

方法很简单:

  • nvidia-smi
  • ollama ps
  • 看日志里的 library=cpu 还是 CUDA 相关信息

2. ollama ps 是第一现场

不要只看模型会不会回答。 会回答不代表用了 GPU。

真正要看的是:

ollama ps

这一步能最快避免“自以为跑在 GPU 上”的误判。

3. 日志里最关键的不是“有 GPU”,而是“用了哪个 library”

像这种日志:

discovering available GPUs...

只能说明它在“找 GPU”。

但真正决定胜负的是:

inference compute id=cpu library=cpu

这说明最后真正用来推理的后端仍然是 CPU。

4. “0 层上 GPU”比“部分层上 GPU”更值得警惕

如果日志里看到:

offloaded 0/33 layers to GPU
runner.vram="0 B"

优先怀疑的就不该是“显存不够”,而应该是:

  • CUDA 后端没加载成功
  • 运行库缺失
  • 库路径不对

5. 环境变量不是越多越好

CUDA_VISIBLE_DEVICES 这种变量,在某些时候有用,但也可能干扰自动探测。 排障时一定要学会做减法:

先让系统回到最简状态,再观察真实现象。

6. 应用自己的运行库路径,往往比驱动本身更容易被忽略

这次最大的坑并不是 NVIDIA 驱动,而是:

/usr/local/lib/ollama

是空目录。

这类问题最难就难在它不显眼。 命令能跑,模型能拉,服务能起,但 GPU 失效。 如果没有顺着日志一路缩小问题范围,很容易永远怀疑错方向。


十、我最终整理出来的一套 Ollama GPU 排障顺序

后面如果再遇到类似问题,我会按这个顺序直接排。

第一步:确认显卡和驱动

nvidia-smi

第二步:确认 Ollama 是否真的在 GPU 上

ollama ps

第三步:看服务和日志

systemctl status ollama --no-pager
journalctl -u ollama -n 200 --no-pager | egrep -i 'cuda|gpu|runner|library|offload|vram'

第四步:判断是“GPU 没识别”还是“GPU 没被用上”

重点看:

  • library=cpu
  • offloaded 0/... layers to GPU
  • runner.vram="0 B"

第五步:排除环境变量干扰

检查服务文件和 override:

systemctl cat ollama
systemctl show ollama --property=Environment

第六步:排除权限问题

id ollama
ls -l /dev/nvidia*

第七步:检查 Ollama 自己的运行库目录

ls -lah /usr/local/lib/ollama
find /usr/local/lib/ollama -type f
ls -lah /usr/lib/ollama
find /usr/lib/ollama -type f | egrep -i 'cuda|ggml|runner|llama'

第八步:如果运行库在 /usr/lib/ollama,但 /usr/local/lib/ollama 是空的

直接修正:

systemctl stop ollama
rm -rf /usr/local/lib/ollama
ln -s /usr/lib/ollama /usr/local/lib/ollama
ldconfig
systemctl start ollama

第九步:重新验证

ollama run qwen3.5:9b
ollama ps
watch -n 1 nvidia-smi

十一、额外提醒:Dify 里接入 Ollama 报错,不一定是模型问题

这次 GPU 问题解决后,我又遇到一个现象:

Dify 里添加 Ollama 模型失败,报的是:

Connection refused

这个问题和 GPU 无关,而是 监听地址 问题。

默认情况下,Ollama 只监听:

127.0.0.1:11434

如果 Dify 在另一台机器上,自然就连不上。

这时候要给 Ollama 增加:

Environment="OLLAMA_HOST=0.0.0.0:11434"

然后重启服务,再检查防火墙和端口放通。

这个小插曲也提醒我:

一个阶段只解决一个问题。

不要把“模型没跑 GPU”和“Dify 接不通”混在一起排。 前者是推理后端问题,后者是网络监听问题,属于两条不同链路。


十二、结语:真正能解决问题的,往往不是命令,而是判断路径

这次 Ollama 排障给我最大的感受是:

很多时候,真正决定能不能解决问题的,不是会不会查一条命令,而是有没有形成一套稳定的判断路径。

这正是我理解的 Polanyi 默会知识

  • 书上不会告诉我先怀疑库路径还是先怀疑驱动
  • 官方文档也不会完整复现我遇到的这个场景
  • 但通过一次次排障,会慢慢形成一种“看到什么现象,就该往哪个方向继续缩小范围”的能力

所以这次我真正收获的,不只是“怎么让 Ollama 跑到 GPU 上”,而是形成了这样一个习惯:

先定性,再缩圈;先排路径,再动系统;先找证据,再下结论。

如果正在本地部署 Ollama,而且也遇到了“模型明明能跑,但就是跑在 CPU 上”的问题,希望这篇文章能帮少走一些弯路。


文末引流

如果也在做:

  • Ollama 本地大模型部署
  • Dify + 本地模型接入
  • GPU 推理环境排障
  • 制造业/运维场景下的大模型落地

欢迎持续关注。 后面我会继续把本地大模型、Dify、NetBox、自动化运维这些真实落地过程中的问题和经验,整理成一套可复现、可实操的系列文章。