news 2026/10/2 12:50:36

Ollama多GPU深度解析:张量并行、调度机制与性能优化实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ollama多GPU深度解析:张量并行、调度机制与性能优化实践

先聊一个我自己踩过的坑。某个周六晚上,我把一台双卡机器搬到工位上,兴致勃勃装了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)单个模型按层拆分到多张GPUOLLAMA_NUM_GPU、CUDA_VISIBLE_DEVICES
模型共存调度多个模型根据显存分配到不同GPUOLLAMA_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、甚至更大参数的量化模型,单卡显存不够,要拆到多张卡。

操作步骤:

  1. 用第2章的方法确认所有卡都可见;
  2. 拉一个单卡装不下的模型,比如llama3.3:70b-q4_K_M;
  3. 启动服务前设置关键环境变量:
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确认。

  1. 模型加载后,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 serve

OLLAMA_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看一眼的习惯。它最能直观反映模型当前落在哪里、占了多少显存、哪个处理器在工作。多卡优化很多时候不是加配置,而是减配置——把不必要的那张卡摘出去,速度反而上来了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 12:50:34

Dubbo3.0 与 Spring Cloud 性能对比

Dubbo 3.0 与 Spring Cloud 性能对比:从协议、连接模型到真实压测本文不是要给出一个“Dubbo 一定比 Spring Cloud 快”的简单结论,而是把 Dubbo 3.0 的 RPC 链路与 Spring Cloud 常见的 REST/HTTP 链路拆开,说明性能差距从哪里来、什么时候会…

作者头像 李华
网站建设 2026/10/2 12:50:32

指针初步学习

指针本质星号的用法传递时的用法本质 就是个“门牌号”把计算机内存想象成一个巨大的小区,里面有一排排一模一样的房子,你在代码里写个 int a 10;,就相当于在这个小区里租了个房子,往里面塞了个写着“10”的纸条。 那指针是啥&a…

作者头像 李华
网站建设 2026/10/2 12:49:44

Firefox 46.0渗透便携版:兼容老系统与经典插件的Web测试利器

简介:火狐46.0渗透便携版是面向渗透测试、护网行动与CTF竞赛人员的集成型火狐浏览器工具包,专为需要快速开展Web漏洞探测、流量审查与插件管理的安全从业者设计。压缩包内共575个文件,整体约69.68MB,除主程序核心组件外&#xff0…

作者头像 李华