当前位置: 首页 > news >正文

双卡 A100 + Ollama 生产部署从安装、踩坑、调优到最终可上线方案

一、为什么这套方案值得认真做

很多人一看到双卡 A100,就会下意识觉得:

这还不简单吗?两张卡,性能肯定够,装上就能飞。

现实恰恰相反。

因为硬件越强,你越容易犯一个错:

误以为资源足够大,所以很多问题都可以被“蛮力掩盖”。

实际上,生产环境里最容易出问题的,从来不是“卡不够强”,而是这些更基础的地方:

  • 服务治理不清晰
  • 端口管理混乱
  • 模型加载策略不稳定
  • 参数没有收敛
  • 调用层分流做得很烂
  • 日志、探活、重试都没做

结果就是:

  • 机器配置看着很豪华
  • 服务偶尔也确实很快
  • 但一上压测、一上并发、一上真实流量,就开始抖

所以这套方案真正的目标,不是“让双卡亮起来”,而是:

把双卡 A100 变成一套可长期稳定提供本地推理服务的生产节点。

二、项目目标:不是能跑,而是能上线

这次方案的目标,最终收敛成了四句话:

1. 服务要稳定

  • 开机自启
  • 异常自动拉起
  • 日志可追踪
  • 进程状态可观测

2. 模型要稳定

  • 模型目录权限正确
  • 模型支持常驻
  • 避免反复冷启动
  • 支持开机自动预热

3. 双卡要真正吃起来

  • 不是一张卡忙、一张卡闲
  • 不是“理论双卡”,而是“实际双实例分流”
  • 请求要能均匀打到两个端口

4. 参数要可收敛

  • 吞吐优先,而不是参数堆满
  • 并发、上下文、KV Cache、队列要一起看
  • 最终形成可解释、可复制的配置

说白了,这套方案追求的不是“演示”,而是“交付”。

三、最终结论先说:双卡 A100 更适合双实例方案

这次实践里,一个非常重要的结论就是:

对于能单卡放下的模型,双卡 A100 上更适合用“双实例双端口分流”,而不是指望一个实例自动把两张卡优雅吃满。

所以最终架构是这样的:

实例一:GPU0

  • 绑定CUDA_VISIBLE_DEVICES=0
  • 监听11434

实例二:GPU1

  • 绑定CUDA_VISIBLE_DEVICES=1
  • 监听11435

上层

  • Python 客户端做轮询分发
  • 支持健康检查
  • 支持失败切换
  • 支持预热感知

这样做的好处非常直接:

  • 每张卡各自维护一份模型副本
  • 两张卡可同时接流量
  • 单实例状态更清晰
  • 调优边界更明确
  • 故障影响更可控

一句话概括就是:

把复杂调度问题,拆成两个简单实例问题。

四、从“安装成功”到“服务可用”,中间至少隔了五个坑

这一段很重要,因为很多文章都喜欢直接跳到“最终配置”,但真正有价值的,恰恰是这些坑。

坑一:systemd服务文件根本没创建好

最开始的报错并不复杂:

Failed toenableunit: Unitfileollama-gpu0.service does not exist.

这说明不是 Ollama 坏了,而是:

  • 服务文件没创建成功
  • 文件名写错了
  • 或者放错目录了

这个坑看起来初级,但实际非常常见。因为一旦你开始用双实例,服务就不再是默认的ollama.service,而是你自己维护的:

  • ollama-gpu0.service
  • ollama-gpu1.service

也就是说,从这一步开始,你就已经进入“自己要对 systemd 配置负责”的阶段了。

坑二:服务文件存在,但进程一启动就退出

后面更进一步,进入了这种状态:

ExecStart=/usr/local/bin/ollama serve(code=exited,status=1/FAILURE)

这时候最容易误判:

  • 是不是路径不对
  • 是不是 Ollama 装坏了
  • 是不是 CUDA 有问题

但真正的正确动作,不是猜,而是:

先确认路径,再看日志。

最终确认结果是:

whichollama /usr/local/bin/ollama

也就是说,这台机器上ExecStart=/usr/local/bin/ollama serve是对的,不需要盲目改成/usr/bin/ollama

这一步其实给了我们一个很实用的经验:

文档路径不是机器路径,机器路径才是真路径。

坑三:11434 端口被占用

后面日志里又出现了一个很经典的问题:

Error: listen tcp 0.0.0.0:11434: bind: address already in use

这说明什么?

说明gpu0起不来不是因为 GPU,不是因为配置,也不是因为权限,而是:

11434 已经被别的进程占了。

而这种情况在 Ollama 场景里特别常见,因为默认ollama.service很可能已经在跑。

也就是说,你一边想搞双实例,一边默认实例还在默默占着11434,那gpu0永远起不来。

这一步最后得出的治理原则非常明确:

既然你决定走双实例,就不要再保留默认单实例服务。

坑四:目录明明写了,为什么还是没权限

端口问题解决之后,最关键的报错终于出现了:

Error: mkdir /data/ollama: permission denied: ensure path elements are traversable

这个错误特别有迷惑性。

很多人第一反应是:

哦,/data/ollama/models没创建,我建一下就好了。

其实不够。

因为这里最关键的不是mkdir,而是后面这个词:

traversable

它的意思是:
运行服务的ollama用户,不仅要对目标目录有权限,还必须能穿过整条上级目录链。

也就是说,只要下面任意一级目录没有可进入权限,就会报错:

  • /
  • /data
  • /data/ollama
  • /data/ollama/models

这也是这次排障里最有代表性的一个收获:

Linux 目录权限问题,很多时候不是目标目录本身,而是父目录链路权限不通。

坑五:双实例起来了,不代表双卡真的工作了

就算服务正常起来,也不代表双卡就真的在吃流量。

因为如果 Python 调用层写成这样:

base_url="http://127.0.0.1:11434"

11435那个实例就只是“存在”,而不是“在工作”。

这一点非常关键。

很多双卡部署失败,不是败在服务层,而是败在调用层:

  • 服务拆成两份了
  • 但业务代码永远只调一个端口
  • 结果就是一张卡累死,一张卡养老

所以真正让双卡工作起来的,不只是两个systemd,而是:

请求分流机制。

五、最终生产版架构长什么样

复盘到最后,整套方案最终定型成这样:

1. 服务层

  • ollama-gpu0.service
  • ollama-gpu1.service

2. 模型层

  • 自定义模型目录:/data/ollama/models
  • ollama用户拥有目录读写权限
  • 模型常驻
  • 支持预热

3. 参数层

  • OLLAMA_KEEP_ALIVE=-1
  • OLLAMA_FLASH_ATTENTION=1
  • OLLAMA_KV_CACHE_TYPE=q8_0
  • OLLAMA_MAX_LOADED_MODELS=1
  • OLLAMA_NUM_PARALLEL=4
  • OLLAMA_MAX_QUEUE=1024
  • OLLAMA_CONTEXT_LENGTH=8192

4. 调用层

  • Python 实例池
  • 轮询分发
  • /api/version轻探活
  • /api/ps检查运行模型
  • 失败自动切换
  • 预热感知

5. 观测层

  • systemctl status
  • journalctl
  • ollama ps
  • nvidia-smi
  • /api/generate返回指标

这套结构不是“看起来复杂”,而是“职责清晰”:

  • 服务负责活着
  • 模型负责热着
  • 调用层负责分流
  • 观测层负责解释问题

六、最终版 systemd 配置

/etc/systemd/system/ollama-gpu0.service

[Unit] Description=Ollama GPU0 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_HOST=0.0.0.0:11434" Environment="CUDA_VISIBLE_DEVICES=0" Environment="OLLAMA_MODELS=/data/ollama/models" Environment="OLLAMA_KEEP_ALIVE=-1" Environment="OLLAMA_FLASH_ATTENTION=1" Environment="OLLAMA_KV_CACHE_TYPE=q8_0" Environment="OLLAMA_MAX_LOADED_MODELS=1" Environment="OLLAMA_NUM_PARALLEL=4" Environment="OLLAMA_MAX_QUEUE=1024" Environment="OLLAMA_CONTEXT_LENGTH=8192" [Install] WantedBy=multi-user.target

/etc/systemd/system/ollama-gpu1.service

[Unit] Description=Ollama GPU1 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_HOST=0.0.0.0:11435" Environment="CUDA_VISIBLE_DEVICES=1" Environment="OLLAMA_MODELS=/data/ollama/models" Environment="OLLAMA_KEEP_ALIVE=-1" Environment="OLLAMA_FLASH_ATTENTION=1" Environment="OLLAMA_KV_CACHE_TYPE=q8_0" Environment="OLLAMA_MAX_LOADED_MODELS=1" Environment="OLLAMA_NUM_PARALLEL=4" Environment="OLLAMA_MAX_QUEUE=1024" Environment="OLLAMA_CONTEXT_LENGTH=8192" [Install] WantedBy=multi-user.target

这一版配置的核心思想很简单:

  • 每张卡一个实例
  • 每个实例一个主模型
  • 模型尽量常驻
  • 并发适度放开
  • 上下文控制在吞吐友好区间
  • 队列留一点缓冲,但不指望它创造性能

七、权限这件事,必须一次性处理到位

这一步是上线前的必做项:

sudomkdir-p/data/ollama/modelssudochown-Rollama:ollama /data/ollamasudochmod755/datasudochmod755/data/ollamasudochmod755/data/ollama/models

再验证:

namei-l/data/ollama/modelssudo-uollamabash-lc'cd /data/ollama/models && touch .perm_test && rm -f .perm_test && echo ok'

为什么我这里特别强调这一块?

因为很多问题看起来像:

  • 模型目录不存在
  • 服务权限有问题
  • systemd 用户不对

本质上,最终往往都能归结为一句话:

目录链路没打通。

而这一类问题,如果你不一次性彻底处理好,它会在每次重启、每次换目录、每次换用户时反复找你。

八、自动预热,是真正让服务变“顺滑”的关键一步

服务起来了,不代表第一批请求就好用。

如果你不做预热,常见现象就是:

  • 刚启动后第一批请求很慢
  • 两个实例首包时延不一致
  • 压测前几轮数据很丑
  • 用户第一波请求体验不好

所以最终加入了一个预热脚本:

/usr/local/bin/ollama-prewarm.sh

#!/usr/bin/env bashset-eMODEL="${1:-gemma3}"forportin1143411435;doecho"[prewarm] checking${port}"curl-sf"http://127.0.0.1:${port}/api/version">/dev/nullecho"[prewarm] loading${MODEL}on${port}"curl-sf"http://127.0.0.1:${port}/api/generate"\-d"{\"model\":\"${MODEL}\",\"keep_alive\":-1}">/dev/nullecho"[prewarm] verifying${MODEL}on${port}"curl-sf"http://127.0.0.1:${port}/api/ps"done

然后再把它接入systemd

/etc/systemd/system/ollama-prewarm.service

[Unit] Description=Prewarm Ollama Models After=ollama-gpu0.service ollama-gpu1.service Wants=ollama-gpu0.service ollama-gpu1.service [Service] Type=oneshot ExecStart=/usr/local/bin/ollama-prewarm.sh gemma3 [Install] WantedBy=multi-user.target

这样做完以后,整条链路就闭环了:

  • 服务启动
  • 模型预热
  • 模型常驻
  • 再开始接流量

这时第一批用户就不再是你的“免费预热器”。

九、吞吐调优的真正核心:不是调参数,而是在做显存预算分配

这是整套方案里最值得记住的一句话:

吞吐调优,本质上是在做显存预算分配。

你在分配的其实是这几项:

1. 给并发多少预算

对应OLLAMA_NUM_PARALLEL

2. 给单请求上下文多少预算

对应OLLAMA_CONTEXT_LENGTH

3. 给模型驻留多少预算

对应keep_alive/OLLAMA_KEEP_ALIVE

4. 给 KV Cache 压缩多少预算

对应OLLAMA_KV_CACHE_TYPE

5. 给高峰排队多少预算

对应OLLAMA_MAX_QUEUE

6. 给多模型竞争多少预算

对应OLLAMA_MAX_LOADED_MODELS

这些参数不是彼此独立的。

尤其这一条必须牢牢记住:

并发和上下文是乘法关系。

也就是说,实例真正承受的压力,不是:

  • 4 并发
  • 8192 上下文

这么简单,而是:

4 × 8192 这个级别的上下文预算。

所以很多调优失败,并不是因为参数不够大,而是因为:

  • 上下文太大
  • 并发太高
  • 两个一起把显存预算吃爆了

最终表现为:

  • 时延抖动
  • 队列堆积
  • 503 overloaded
  • CPU offload
  • 双卡看起来很忙,但整体吞吐不高

十、真正会看压测结果的人,看的是“慢在哪里”

很多人压测只看一句:

  • 平均耗时多少秒

这不够。

真正有价值的是接口返回里的这些字段:

  • total_duration
  • load_duration
  • prompt_eval_duration
  • eval_duration

它们分别能告诉你:

1. 是不是模型没热好

load_duration

2. 是不是输入太长

prompt_eval_duration

3. 是不是生成本身慢

eval_duration

4. 是不是整体链路在抖

total_duration

你只有把这几个值一起看,才能知道:

  • 慢是因为没预热
  • 慢是因为 prompt 太大
  • 慢是因为生成长度太长
  • 慢是因为实例过载

否则你只知道“慢”,却不知道“为什么慢”。

十一、ollama psnvidia-smi,一个都不能少

很多人喜欢只看nvidia-smi

它当然重要,但它只能告诉你:

  • 显存占多少
  • GPU 利用率怎么样

它不能直接告诉你:

  • 模型是不是 100% GPU
  • 有没有偷偷 offload 到 CPU
  • 当前实例实际拿到的上下文是多少

这时候就必须结合:

ollamaps

和:

watch-n1nvidia-smi

一起看。

你真正应该关心的是:

  • ollama ps里模型是不是100% GPU
  • CONTEXT是多少
  • 两个实例是不是都挂了模型
  • 两张卡是不是都在稳定出力

真正的调优,不是单看一个指标,而是把 API 指标、运行状态、GPU 状态拼起来看。

十二、Python 调用层,决定了双卡到底是不是双卡

服务层解决的是“实例存在”。

调用层解决的是“实例工作”。

最终的 Python 调用方案要具备四个能力:

1. 轮询分流

不能永远打一个端口

2. 探活

先看/api/version

3. 失败切换

一个实例失败时自动切到另一个

4. 预热感知

两个实例都要先热起来

最终思路其实很朴素:

  • 准备两个客户端
  • 轮询拿客户端
  • 先探活
  • 失败标记不健康
  • 过一段时间再恢复探测

这套东西写出来不复杂,但价值非常大。因为它让双实例真正变成了:

一个可调度的本地推理池。

十三、上线前的最终检查清单

如果要把这套方案交付上线,我建议最后按下面这张清单走一遍。

服务层

  • ollama-gpu0.service正常运行
  • ollama-gpu1.service正常运行
  • ollama-prewarm.service可正常执行
  • 默认ollama.service已停用

目录层

  • /data/ollama/models已创建
  • ollama用户对目录有读写权限
  • 父目录链路可遍历

端口层

  • 11434正常监听
  • 11435正常监听
  • 没有额外进程占端口

模型层

  • 模型可正常拉取
  • 两个实例都能预热
  • /api/ps能看到运行模型
  • 模型保持常驻

性能层

  • ollama ps显示100% GPU
  • 两张 A100 都有稳定利用率
  • load_duration在预热后明显下降
  • 没有持续性 503 overloaded

调用层

  • Python 客户端支持轮询
  • Python 客户端支持失败切换
  • Python 客户端支持探活
  • 压测时两个实例都能吃到请求

只要这一套检查清单都过了,这套方案基本就已经脱离“实验环境”,进入“可上线环境”了。

十四、这次实践最重要的五个收获

最后,把整次方案压成五句话。

收获一:默认服务和自定义双实例不能混用

否则端口冲突几乎是必然的。

收获二:目录权限问题,很多时候卡在父目录

不是目标目录不存在,而是ollama用户过不去。

收获三:双卡要真正工作,调用层必须做分流

服务拆两份没意义,请求不分流,一样是一张卡干活。

收获四:吞吐调优本质是显存预算分配

不是把参数调大,而是让每一份显存花得值。

收获五:生产化的关键,不是服务能起,而是服务能一直稳

systemd、常驻、预热、探活、重试、压测、观测,缺一个都可能让“看起来能跑”的方案,在真实流量里变得不好用。

十五、结尾:这套方案到底算不算完成

如果只是从“把 Ollama 安装到服务器上”这个角度看,这套方案早就该结束了。

但如果你从“能不能作为一套真正可用的本地推理服务上线”这个角度看,到这一篇,才算真正收官。

因为我们最终交付的,已经不是一条命令、一个模型、一个端口。

而是一整套完整能力:

  • 双实例服务治理
  • 双卡资源利用
  • 模型常驻与预热
  • 目录权限治理
  • Python 调用封装
  • 轮询分流与失败切换
  • 压测与性能判读
  • 最终上线检查清单

这才是一套像样的生产方案。

说到底,真正的技术能力,不是“你会不会装 Ollama”,而是:

当它不顺的时候,你能不能把它一步一步拉回到正确的位置。

而这次双卡 A100 + Ollama 的完整实践,本质上就是这样一场从“能装”,走到“能打”的过程。

http://www.cnnetsun.cn/news/1727221.html

相关文章:

  • 智慧车站三维空间智能管控系统白皮书——构建“全域感知 × 连续认知 × 动态调度”的交通枢纽空间智能中枢
  • 别再只做静态分析了!用DPABI解锁小鼠脑功能动态连接(Temporal Dynamic Analysis详解)
  • 用Maven打包SpringBoot项目的正确姿势:从配置到验证的保姆级指南
  • MacBook上5分钟搞定JMeter环境:从JDK检查到永久汉化(保姆级避坑指南)
  • 别再只盯着比特币挖矿了!手把手带你搞懂PoW、PoS、DPoS到底该怎么选(附实战场景分析)
  • Vue + Iframe 实战:打造企业级流程配置中心
  • 沿海城市创新力为何更强?
  • Lean语言+AI入门基础教程(非常详细),编译器验证数学证明看这篇就够了!
  • 塞尔达存档定制工具:解锁海拉鲁冒险的无限可能
  • 李慕婉-仙逆-造相Z-Turbo 生成Matlab算法脚本:从数学公式到可执行代码
  • 告别GUI:用纯脚本高效处理GRACE RL06数据的完整工作流分享
  • WinThumbsPreloader:让Windows图片预览提速80%的缓存优化工具
  • 多平台资源高效获取解决媒体下载难题:res-downloader的全方位应用指南
  • 直流电机电流采集避坑指南:基于STM32F302R8和X-NUCLEO-IHM07M1的ADC配置详解
  • PentAGI - AI 渗透测试工具详解
  • AI辅助开发新体验:让快马平台智能解析并生成复杂网站的claw hub爬虫策略
  • GD32F10x外部晶振配置108MHz系统时钟的常见问题与解决方案
  • 博士论文“智囊团”:好写作AI开启学术新境界
  • 救命!这些毕设太好抄了,3000+毕设案例推荐第1029期
  • 国产芯片LT8619C在智能投影仪中的应用:从HDMI到RGB的完整信号链解析
  • Linux新手必看:CentOS启动卡在initramfs的完整自救指南
  • 用HTML5+CSS3打造环保主题网页:从零开始实现响应式布局(附完整代码)
  • 实战应用:基于快马开发应对复杂依赖的openclaw深度卸载解决方案
  • 实战应用:将ubuntu上的openclaw抓取算法部署为web服务,快马平台一键搞定
  • WPF OpenFileDialog高级功能实战:从基础配置到企业级应用
  • 窗口半透明增强:为Windows文件管理器注入视觉新体验
  • Mac新手必看:Homebrew国内镜像配置全攻略(含常见问题解决方案)
  • Token 烧钱?OpenClaw 这几个配置让我省了一半开销
  • Karpathy 开源了 Agent + Obsidian 个人知识库, 超级有启发
  • Vue前端项目实战:打造浏览器端DeepSeek-OCR应用