双卡 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 服务文件根本没创建好

最开始的报错并不复杂:

bash 复制代码
Failed to enable unit: Unit file ollama-gpu0.service does not exist.

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

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

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

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

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

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

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

bash 复制代码
ExecStart=/usr/local/bin/ollama serve (code=exited, status=1/FAILURE)

这时候最容易误判:

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

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

先确认路径,再看日志。

最终确认结果是:

bash 复制代码
which ollama
/usr/local/bin/ollama

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

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

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

坑三:11434 端口被占用

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

text 复制代码
Error: listen tcp 0.0.0.0:11434: bind: address already in use

这说明什么?

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

11434 已经被别的进程占了。

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

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

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

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

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

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

text 复制代码
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 调用层写成这样:

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

ini 复制代码
[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

ini 复制代码
[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

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

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

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

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

bash 复制代码
sudo mkdir -p /data/ollama/models
sudo chown -R ollama:ollama /data/ollama
sudo chmod 755 /data
sudo chmod 755 /data/ollama
sudo chmod 755 /data/ollama/models

再验证:

bash 复制代码
namei -l /data/ollama/models
sudo -u ollama bash -lc 'cd /data/ollama/models && touch .perm_test && rm -f .perm_test && echo ok'

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

因为很多问题看起来像:

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

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

目录链路没打通。

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

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

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

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

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

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

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

bash 复制代码
#!/usr/bin/env bash
set -e

MODEL="${1:-gemma3}"

for port in 11434 11435; do
  echo "[prewarm] checking ${port}"
  curl -sf "http://127.0.0.1:${port}/api/version" > /dev/null

  echo "[prewarm] loading ${MODEL} on ${port}"
  curl -sf "http://127.0.0.1:${port}/api/generate" \
    -d "{\"model\":\"${MODEL}\",\"keep_alive\":-1}" > /dev/null

  echo "[prewarm] verifying ${MODEL} on ${port}"
  curl -sf "http://127.0.0.1:${port}/api/ps"
done

然后再把它接入 systemd

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

ini 复制代码
[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
  • 当前实例实际拿到的上下文是多少

这时候就必须结合:

bash 复制代码
ollama ps

和:

bash 复制代码
watch -n 1 nvidia-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 的完整实践,本质上就是这样一场从"能装",走到"能打"的过程。

相关推荐
计算机安禾2 小时前
【数据结构与算法】第30篇:哈希表(Hash Table)
数据结构·学习·算法·哈希算法·散列表·visual studio
AC赳赳老秦2 小时前
OpenClaw阿里云部署实操:多Agent协同,打造云端自动化工作流
人工智能·阿里云·数据挖掘·自动化·云计算·deepseek·openclaw
xiaoye-duck2 小时前
《算法题讲解指南:动态规划算法--子序列问题(附总结)》--32.最长的斐波那契子序列的长度,33.最长等差数列,34.等差数列划分II-子序列
c++·算法·动态规划
Linux猿2 小时前
云朵照片数据集,YOLO 目标检测 | 附数据集
人工智能·yolo·目标检测·图像分类·目标检测数据集·yolo目标检测·云朵照片数据集
Cosolar2 小时前
超越基础 CRUD:LangChain-Chroma 在高并发场景下的架构设计与瓶颈突破
人工智能·后端·面试
Omics Pro2 小时前
Cell|全球微生物群落整合数据库
人工智能·语言模型·自然语言处理·数据挖掘·数据分析
SPC的存折2 小时前
1、MySQL数据库基础
linux·运维·数据库·mysql
AI智域边界 - Alvin Cho2 小时前
attas 现已开源,探索金融 AI 下一步该走向哪里
人工智能·金融
管二狗赶快去工作!2 小时前
体系结构论文(九十):Automated Multi-Agent Workflows for RTL Design
人工智能