先聊一个我自己踩过的坑。某个周六晚上,我把一台双卡机器搬到工位上,兴致勃勃装了Ollama,拉下来一个70B的量化模型,心想两张4090怎么着也比单卡快。结果跑起来一看,两张卡确实都占了显存,但利用率一个天上一个地下,第二张卡时不时就在摸鱼。更让我没想到的是,后来我随便跑一个7B小模型,第二张卡直接原地罢工,连显存都不碰。那晚我几乎把网上关于Ollama多GPU的帖子翻了个底朝天,最后发现真相很简单:Ollama对多张GPU的调度逻辑,和大多数人直觉里的“多卡自动加速”根本不是一回事。
这篇文章我就把多GPU跑Ollama这件事讲透,包括它底层到底怎么切分模型、哪些环境变量真正起作用、三种多卡玩法分别适用什么场景、性能瓶颈出在哪里,以及一张完整的排查链路。无论你是想把70B这种单卡装不下的模型拆到两张卡上,还是想在一台多卡机器上同时服务多个模型、多个用户,都能在里面找到直接能用的配置。
1. 先搞清楚Ollama的多GPU调度机制:一张卡不够时它到底做了什么
1.1 “按层拆”的张量并行,才是Ollama多卡的本质
Ollama本身不实现训练框架,它做推理的后端核心是llama.cpp那套引擎。在这个引擎里,多GPU运行一个模型靠的是张量并行(Tensor Parallelism,TP)。大模型的GGUF权重文件里,本质上是几十层Transformer block,每层内部又分attention、MLP这些子模块。张量并行的思路就是把模型按层切开——比如一个70B模型有80层,两张卡就各分40层。推理的时候,前向计算每过完一层,就要把中间激活值广播到另一张卡上,两边算完再汇总,才能进入下一层。
这个机制决定了多卡性能的上限完全依赖于卡间通信质量。你可以把每张GPU想象成一个车间,车间内部流水线跑得飞快,但两个车间之间只有一条窄传送带,每加工完一步都要把半成品搬过去再搬回来,效率自然会被拖累。所以同一个模型,在NVLink互联的卡上跑和走PCIe普通通道跑,体验完全是两个级别。
1.2 “模型跨卡”和“模型各占一卡”是两件不同的事
很多刚上手的人说“多GPU跑Ollama”,其实可能同时表达两种需求,而且经常混在一起:
- 一个模型太大,单卡显存放不下,需要拆到多张卡上一起跑,这叫张量并行;
- 一台机器同时部署了好几个模型,希望它们分散到不同卡上,不要全挤在一张卡里。
这两个诉求的实现路径完全不同。模型跨卡由Ollama的GPU加载逻辑控制,关键环境变量是OLLAMA_NUM_GPU;而模型在卡之间的分配,则依赖调度器根据每张卡的剩余显存、OLLAMA_MAX_LOADED_MODELS和OLLAMA_KEEP_ALIVE综合决定。
还有一个高频误解:以为一张卡装一个模型,两个并发请求就会分别派给两张卡,实现一人一张卡的处理。真实情况是,Ollama对同一个模型的并发请求,默认是在同一个模型实例里通过上下文切换轮流处理的,不会自动变成“两个GPU副本各处理一条请求”。要真正做到按卡分流、提升吞吐,反而要手动开多实例——这一点我放到第3章的方案三里详细讲。
| 概念 | 含义 | 配置入口 |
|---|---|---|
| 张量并行(TP) | 单个模型按层拆分到多张GPU | OLLAMA_NUM_GPU、CUDA_VISIBLE_DEVICES |
| 模型共存调度 | 多个模型根据显存分配到不同GPU | OLLAMA_MAX_LOADED_MODELS、OLLAMA_KEEP_ALIVE |
| 数据并行(多实例) | 请求分摊到不同GPU上的多个实例 | 多个ollama serve进程、端口隔离 |
这张表建议直接存下来,后面所有配置都逃不出这三个方向。
2. 开工前的环境检查:驱动、容器与GPU可见性一个都不能少
Ollama不帮你装驱动,它只是通过CUDA或ROCm运行时去调用GPU。所以很多“多卡不生效”的问题,最终查下来根因根本不在Ollama,而在环境层。这一章我不会面面俱到,只写我实际遇过、也最容易被忽略的几个点。
2.1 NVIDIA平台:驱动和容器是重灾区
先说宿主机。NVIDIA的GPU,宿主机只需要装好驱动,Ollama的官方镜像内部已经带了CUDA runtime,不需要你另外在容器里装CUDA。驱动是否正常,一条命令就能确认:
nvidia-smi如果这里能列出所有GPU,说明驱动层面没问题。接着看容器。很多人习惯用docker run --gpus all启动Ollama,但如果宿主机没装NVIDIA Container Toolkit,这条命令会直接报错或者静默忽略GPU。更隐蔽的是用docker-compose的时候,deploy.resources.reservations.devices配置里GPU数量写错,容器里就只能看到一张卡。
每次启动容器后,建议立刻进容器确认一下:
docker run --rm --gpus all ollama/ollama nvidia-smi输出里能看到几张卡,再往下走。如果这里就只剩一张卡,后面所有Ollama配置都白搭,问题先回到容器层排查。
2.2 AMD平台:ROCm的支持边界
如果你是A卡用户,Ollama同样支持,但需要拉ollama/ollama:rocm这个标签的镜像。AMD这边多卡问题更多,驱动版本、ROCm版本和显卡代际的匹配关系比较敏感,rog smi先确认系统识别了几张卡是第一步。
这里提醒一点:WSL2里的GPU透传行为和原生Linux不完全一样,你在WSL2里能看到的GPU,不一定代表原生Linux环境会有同样的表现。如果要在生产环境长期跑,建议优先考虑原生Linux,而不是WSL2,省得后面排查环境差异耗费大量时间。
2.3 确认Ollama真的看到了所有GPU
环境变量OLLAMA_DEBUG=1是非常有用的诊断开关。启动服务时这样跑:
OLLAMA_DEBUG=1 ollama serve正常识别多卡时,启动日志里会多次出现类似inference compute id=0、inference compute id=1的记录。如果你只看到一条,说明Ollama只识别了一张卡,这时候哪怕你把它吹上天,它也不会用第二张。
日常运行中,可以用ollama ps看当前模型加载到了哪张卡上,输出里的PROCESSOR列会直接标明是CPU还是GPU。如果再配合nvidia-smi dmon或nvtop实时看显存和利用率,环境层基本就能做到滴水不漏。
3. 多GPU的三种玩法:拆大模型、卡上分家、进程分流
这一章是实操核心。三种玩法解决三类问题,我按场景拆分来讲。
3.1 方案一:单模型自动跨卡,让一张卡装不下的模型跑起来
适用场景:70B、甚至更大参数的量化模型,单卡显存不够,要拆到多张卡。
操作步骤:
- 用第2章的方法确认所有卡都可见;
- 拉一个单卡装不下的模型,比如
llama3.3:70b-q4_K_M; - 启动服务前设置关键环境变量:
export OLLAMA_NUM_GPU=0,1 ollama serve这里有个容易踩的版本坑:Ollama较新版本(0.5.x之后)中,OLLAMA_NUM_GPU的值是GPU序号列表,比如0,1表示用第0和第1张卡;而在旧版本里,这个变量可能表示“把多少层放到GPU”,比如OLLAMA_NUM_GPU=40意味着只把40层丢给GPU。如果你照着网上的老帖子设置了数字,新版本可能根本不会按你的预期走。最稳妥的做法是启动后立刻ollama ps确认。
- 模型加载后,
ollama ps里的PROCESSOR列如果显示GPU并且跨两张卡,说明TP生效了。
这套配置下,Ollama会尽量把模型权重和KV Cache都塞进显存,塞不下才溢出到CPU。如果你的卡之间存在NVLink,体验会非常好;如果只是PCIe互联,性能可能比“单卡+部分CPU offload”快不了多少,这一点我在第4章会专门展开。
3.2 方案二:多模型各占一张卡,让调度器按显存分家
适用场景:一台多卡机器上同时跑好几个模型,希望互不干扰。
实际操作上不需要强制指定谁去哪张卡,Ollama的调度器会根据每个模型的显存需求和每张卡的剩余空间自动决定。你只需要给足调度器空间:
export OLLAMA_MAX_LOADED_MODELS=2 export OLLAMA_KEEP_ALIVE=30m ollama serveOLLAMA_MAX_LOADED_MODELS决定了同时最多驻留几个模型,超过这个数会按LRU策略卸载最久没用的模型。OLLAMA_KEEP_ALIVE控制模型在显存里的保留时间,如果设为-1就是永久驻留,适合模型切换不频繁的场景。
跑起来之后用ollama ps观察,正常情况下不同模型会落在不同的GPU上。但要注意一个前提:这些模型本身都要小于单卡显存。如果其中一个模型大到单卡放不下,调度器会优先采用张量并行把它拆到多卡,而不是强行“各占一卡”。
3.3 方案三:多实例数据并行,多用户并发的最优解
适用场景:显存足够,但要同时服务大量请求,单实例排队比较严重。
原理不复杂:一个ollama serve进程处理一份模型,再多的并发请求都会在这个进程里排队。如果你有两卡,其实可以硬生生拆成两个独立服务,让请求分摊出去:
# 第一个实例,绑定GPU0,端口11434 CUDA_VISIBLE_DEVICES=0 OLLAMA_HOST=0.0.0.0:11434 ollama serve # 第二个实例,绑定GPU1,端口11435 CUDA_VISIBLE_DEVICES=1 OLLAMA_HOST=0.0.0.0:11435 ollama serve两个实例可以加载同一个模型,也可以加载不同模型。上层客户端轮询两个端口,或者加一层负载均衡,就能把请求分散到两张卡上。
我实际测试过一种场景:16并发请求、长上下文下去打一个模型实例,响应时间会明显拉长,甚至出现超时;拆成两个实例后,每个实例只有8并发,排队时间立刻降下来。代价是要维护多个服务端口和模型副本,复杂度高一些,但对吞吐的改善是立竿见影的。
3.4 一张卡装得下的模型,开多卡通常只会更慢
这是最反直觉的一点,也值得单独拿来强调。许多人的第一反应是“卡越多越快”,但在以Transformer层为单位做TP的推理场景下,这句话是错的。
我用一个7B模型做过简单对比:单卡跑7B Q4,生成速度约60 token/s;强制双卡TP后,速度反而掉到45 token/s左右。原因就是卡的互联带宽成为了瓶颈,每一层都要做一次all-reduce通信,通信开销大过了并行计算带来的收益。当模型本身一张卡就能装下时,多卡没有任何正向帮助,反而添乱。
所以我的建议很明确:能塞进单卡的模型,就一定不要开TP。如果设置了OLLAMA_NUM_GPU=0,1,有些版本的Ollama可能会强行把模型拆到两张卡上,哪怕显存够用。这时候反而应该显式指定CUDA_VISIBLE_DEVICES=0,让模型只在第一张卡上跑。
4. 显存、量化与跨卡通信:影响多卡性能的三个真正变量
这一章回答一个核心问题:我用TP把模型拆到多卡了,为什么速度还是上不来?除了卡数,还有三个变量决定了最终体验。
4.1 显存计算模型:模型权重、KV Cache与运行时开销
显存需求不是“模型文件多大就占多少”,它由三部分构成:
- 模型权重:基本等于GGUF文件实际大小,比如Q4_K_M量化的70B模型大约40GB;
- KV Cache:与上下文长度成正比,也和并发数成正比;
- 运行时开销:激活值、临时缓冲区等。
KV Cache的估算可以套一个简化公式:
KV Cache ≈ 2 × 层数 × KV头数量 × 头维度 × 上下文长度 × 每个元素字节数举例来说,70B Q4_K_M模型配8K上下文时,总显存需求大概在46~50GB左右。两张24GB的4090合计48GB,看着凑合,但几乎没有给并发和长上下文留余量。这也是为什么很多人在多卡跑大模型时,一开长上下文就报显存不足,或者卡到无法接受。6G显存的朋友更要注意,6G卡比较稳妥的选择是7B或8B模型再量化到Q4(比如qwen2.5:7b-instruct-q4_K_M),同时把num_ctx限制在4K以内,否则显存大概率会被KV Cache挤爆。
4.2 跨卡通信带宽决定性能天花板
TP模式下,每一层Transformer的前向计算都需要一次跨卡同步,通信量跟模型的hidden_size和batch大小直接相关。硬件层面的带宽差距非常悬殊:
| 互联方式 | 典型带宽 | 实际感受 |
|---|---|---|
| PCIe 4.0 x16 | 双向约64GB/s | 小模型TP反而降速 |
| NVLink 3.0(多卡) | 最高约600GB/s | 大模型TP可用性明显提升 |
| 多机以太网 | 1~100Gb/s | 基本不适合做TP |
Ollama要跨节点跑TP是不现实的,它就是单机工具。多机场景下,做法通常是在每台机器上各部署一个Ollama实例,上层做请求分发,而不是把模型切成十几份跨机器跑。
实际调优时,如果双卡TP比单卡加CPU offload只快一点点,可以尝试缩小上下文长度,或者把OLLAMA_NUM_PARALLEL调成1,减少KV Cache后看看吞吐变化。另外,较新版本的Ollama默认开启了flash attention,这能显著减少KV Cache的读写压力,间接缓解跨卡通信瓶颈。如果遇到性能异常,也可以确认一下是否被旧版本或模型模板里的参数关掉了。
4.3 和“GPU微调大模型”不是一回事,别混淆方向
网上搜索Ollama多GPU时,经常会看到“GPU微调大模型”的内容,但Ollama本身是做推理的,不是训练框架。微调要的是数据并行、梯度同步那一套,跑的是PyTorch、DeepSpeed、Megatron之类的训练栈;而Ollama的多卡并行是推理侧的张量并行。这两个方向完全不同,千万别拿着Ollama去干微调的活。如果你想微调,直接转向训练框架;如果只是想把大模型部署成API服务,那Ollama多卡这套方案才是你要的。
5. 多卡跑不动、卡不干活?按这条链路排查
多GPU排错最忌讳瞎猜。我总结了一条固定排查链路,遇到问题按顺序走,基本都能定位。
5.1 排查主线:日志、ollama ps、显存监控三连
第一步先开debug日志:
OLLAMA_DEBUG=1 ollama serve重点看启动阶段是不是把所有GPU都列出来了。如果日志里只有一个GPU,问题大概率在环境层,检查驱动、容器参数、CUDA_VISIBLE_DEVICES有没有误设置。
第二步,在模型加载后执行ollama ps:
ollama ps看PROCESSOR列和显存占用。如果显示模型加载在GPU上但没跨卡,说明TP没生效,或者模型太小单卡就装完了。
第三步,开着nvidia-smi dmon或nvtop观察实时利用率。注意区分“显存占用高”和“GPU利用率高”,显存占用高只说明权重放到了显存里,不代表计算在正常跑。如果显存占满了但利用率很低,往往是CPU offload的层在拖后腿,或者请求本身太小,GPU饿着肚子等数据。
5.2 常见症状与原因对照表
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 模型加载了,但第二张卡显存/利用率一直为0 | 模型权重单卡就够放;TP未生效 | 确认OLLAMA_NUM_GPU;换更大的模型或更低量化 |
| 两张卡利用率都不高,但响应很慢 | CPU offload层数过多;跨卡通信瓶颈 | 查看日志中offload层数;降低num_ctx;考虑NVLink |
| 设置了环境变量但完全没效果 | Windows服务/托盘进程没完全退出;docker里没传环境变量 | 彻底退出Ollama再重启;docker加-e参数 |
ollama run报file does not exist | 模型文件下载不完整;或指定了不存在的标签 | 删除本地模型后重新拉取;ollama list确认标签 |
| 下载模型非常慢 | 默认模型仓库源不稳定 | 下载GGUF后用Modelfile本地导入;使用可用的模型下载加速方式 |
| 系统盘被模型文件塞满 | 模型默认存在~/.ollama/models | 设置OLLAMA_MODELS到数据盘,迁移目录后重启 |
| 6G显存到底能跑什么模型 | 显存上限 | 7B/8B Q4量化模型,严格限制num_ctx |
这里面有两个点值得单独展开。
第一,环境变量不生效的问题,在Windows上特别坑。你在系统设置里改了环境变量,但Ollama是以系统托盘方式后台运行的,不彻底退出托盘图标,进程就不会重新读取新环境变量。改完一定先完全退出Ollama,再重新启动。容器环境则更好办,docker run的时候加-e OLLAMA_NUM_GPU=0,1就行。
第二,模型下载慢和路径迁移。模型文件默认存放在~/.ollama/models,长期跑下来系统盘很容易爆。迁移方式并不复杂:先完全停止Ollama,把整个目录移动到目标盘,然后设置OLLAMA_MODELS=/your/new/path,再启动服务即可。另外,如果官方源下载大模型慢到无法接受,很多人会选择先用其他方式下载GGUF文件,再通过Modelfile本地导入,这在Ollama里是完全受支持的路径,也能绕开下载慢的问题。
5.3 一次完整推凶:8卡机器为何只用上1张
最后分享一个我自己处理过的案例,完整还原排查链路。某次帮朋友调一台8卡服务器,驱动的nvidia-smi一切正常,8张卡全部识别,docker也是--gpus all启动的,结果Ollama加载70B模型后,死活只用了1张卡。
第一步我开OLLAMA_DEBUG=1,日志里果然只出现了一个inference compute id=0。说明Ollama层面只看到一张卡,问题不在模型调度,而在更底层。
第二步检查容器内GPU可见性,进容器跑nvidia-smi,发现容器里确实只看到1张卡。这就很蹊跷了,因为宿主机上能看到8张,命令也是--gpus all。最后翻了docker-compose配置才发现,deploy.resources.reservations.devices里device_ids只列了一个"0",等于手动把GPU范围限制死了。改成device_ids: ["0","1","2","3","4","5","6","7"]之后,Ollama日志里8张卡全部出现。
本以为到此结束,结果继续加载70B模型,发现Ollama还是只把权重放到了GPU0。这次再检查,是因为OLLAMA_NUM_GPU环境变量被某份旧教程改成了OLLAMA_NUM_GPU=1——旧版本语义里的“只放1层到GPU”或“限制单卡”,放到新版本里直接限制了GPU数量。清掉这个变量、改成显式指定OLLAMA_NUM_GPU=0,1,2,3,4,5,6,7之后,模型才真正跨卡加载。
最后一步才是性能问题。8张卡虽然负载均衡了,但有些卡之间走的是PCIe交换机,没有NVLink,TP的通信延迟导致生成速度远低于预期。这时候已经不是“能不能跨卡”的问题,而是“跨卡值不值”的问题。最终方案是把模型按卡分组,拆成两个Ollama实例,各吃4张卡,吞吐才稳定下来。
6. 顺手抄的配置模板和一点个人经验
聊到这儿,多卡跑Ollama的机制、玩法和排查思路都过了一遍。最后给一个可以直接抄的组合模板,覆盖大部分单机多卡场景:
# 单模型跨卡TP:多个模型同时驻留,模型文件放数据盘 export OLLAMA_MODELS=/data/ollama/models export OLLAMA_NUM_GPU=0,1,2,3 export OLLAMA_MAX_LOADED_MODELS=2 export OLLAMA_KEEP_ALIVE=30m export OLLAMA_DEBUG=0 ollama serve如果是纯并发服务场景,可以换成多实例模板:
# 每卡一个实例 CUDA_VISIBLE_DEVICES=0 OLLAMA_HOST=0.0.0.0:11434 ollama serve CUDA_VISIBLE_DEVICES=1 OLLAMA_HOST=0.0.0.0:11435 ollama serve我自己的体会是,多卡跑Ollama有两个极端:
一是别盲目追求“所有模型都跨卡”,小模型单卡优先,大模型才考虑TP,而TP又得先确认卡间互联带宽够不够;二是如果你在正经做服务,多实例数据并行往往比单实例TP更实用,因为它既不要求NVLink,又能直接摊薄并发压力。
最后再补一个小技巧:任何一次变更环境变量后,都养成ollama ps看一眼的习惯。它最能直观反映模型当前落在哪里、占了多少显存、哪个处理器在工作。多卡优化很多时候不是加配置,而是减配置——把不必要的那张卡摘出去,速度反而上来了。