一、为什么双实例比"一个实例看见两张卡"更适合吞吐优先场景
如果你的模型能完整装进单张 A100,那么对"整机总吞吐"来说,双实例双端口 往往比"单实例双卡自动调度"更直观、也更好控。原因很简单:你把 11434 固定给 GPU0,把 11435 固定给 GPU1,请求层再做轮询,这样两张卡会各自维护一份模型副本,各自处理一部分流量。Ollama 的 API 本身就是本地 HTTP 服务,天然适合被上层反向代理接入。(Ollama 文档)
这套思路的核心不是"花哨",而是可控 。单实例跨卡更适合"模型太大,单卡放不下";双实例更适合"模型能单卡放下,但我要吃满整机并发"。一旦你用轮询把流量稳定摊到两个实例上,双卡就不再是"理论资源",而会变成真正可被业务吞掉的吞吐能力。这个判断是基于 Ollama 的本地 API 形态和多实例部署方式做出的工程化设计。(Ollama 文档)
二、最小可用架构:11434 + 11435 + 一个入口层
推荐的结构非常简单:
ollama-gpu0:127.0.0.1:11434ollama-gpu1:127.0.0.1:11435- 一个统一入口:Nginx 或 HAProxy
- 业务系统只调用统一入口地址
这样做的好处是,业务代码永远只认一个入口,而后端两个 Ollama 实例可以独立重启、独立摘除、独立预热。只要入口层健康检查配置合理,一个实例挂了,流量会自动被送到另一个实例。HAProxy 官方文档明确把健康检查定义为"只让健康服务器保留在负载均衡轮转中";Nginx 也支持 upstream 服务器组,并能基于失败次数与超时做被动摘除。(HAProxy Technologies)
三、先别急着上 Nginx,先把两个实例本身验证通
反向代理只是"调度层",前提是你的两个 Ollama 实例都能独立工作。先分别检查:
bash
curl http://127.0.0.1:11434/api/version
curl http://127.0.0.1:11435/api/version
/api/version 是最轻量的可达性检查接口,适合拿来做存活探测;如果你想看当前实例内存里有没有已经装载的模型,就查 /api/ps。这两个接口都属于官方 API。(Ollama 文档)
再分别试一条最小生成请求:
bash
curl http://127.0.0.1:11434/api/generate -d '{
"model": "gemma3",
"prompt": "你好",
"stream": false
}'
curl http://127.0.0.1:11435/api/generate -d '{
"model": "gemma3",
"prompt": "你好",
"stream": false
}'
Ollama 的 /api/generate 接口要求至少提供 model 和 prompt;stream 默认为 true,压测或联调阶段把它设成 false 更方便看完整返回。响应里还会带上 total_duration、load_duration、eval_count、eval_duration 等指标,这些字段非常适合做性能分析。(Ollama 文档)
四、为什么"预热"是双实例吞吐稳定的关键
很多人在压测时发现一个很奇怪的现象:第一批请求慢,后面快;或者两台实例里总有一台突然比另一台慢一大截。问题往往不在显卡,而在模型装载与预热。
Ollama 官方 FAQ 明确提到:你可以通过发送一个"空请求"来预加载模型,这对 /api/generate 和 /api/chat 都生效。也就是说,在正式接流量之前,先对 11434 和 11435 各打一遍预热请求,让两边都把模型拉进内存,再开始分流,首包延迟会明显更稳定。(Ollama 文档)
例如:
bash
curl http://127.0.0.1:11434/api/generate -d '{"model":"gemma3"}'
curl http://127.0.0.1:11435/api/generate -d '{"model":"gemma3"}'
如果你本身已经配置了 OLLAMA_KEEP_ALIVE=-1,那预热效果会更明显,因为模型不会很快被卸掉。这样做的本质,就是把"第一次装载的代价"提前支付掉,而不是让真实用户帮你承担。相关预热方式来自官方 FAQ,模型常驻则来自运行参数设计。(Ollama 文档)
五、入口层选型:Nginx 够轻,HAProxy 更像"正经负载均衡器"
1)Nginx 的优点
Nginx 非常适合已经在线上普遍使用它的团队。它的 upstream 机制可以定义后端服务器组,再通过 proxy_pass 统一转发。官方文档说明,Nginx 支持多种负载分发方式,其中最常见的就是默认轮询;同时,max_fails 和 fail_timeout 可以让后端在连续失败后被暂时标记为不可用。(Nginx)
2)Nginx 的限制
很多人以为 Nginx 开源版就能直接做完整主动健康检查,其实不是。ngx_http_upstream_hc_module 这个"周期性主动健康检查"模块是 商业订阅版本 才有;开源常见做法更多是依赖被动失败摘除,也就是通过真实请求失败次数来判断后端是否有问题。(Nginx)
3)HAProxy 的优点
如果你更看重"负载均衡本身",HAProxy 会更像专业工具。它原生支持健康检查;官方文档明确说明,健康检查的目的是确保只有健康服务器保留在轮转中,而且 balance 可以选择 roundrobin、leastconn 等算法。对于 Ollama 这种长连接不算多、但单请求较重的推理服务,roundrobin 往往足够简单有效;如果你更关心当前连接数均衡,可以考虑 leastconn。(HAProxy Technologies)
六、Nginx 方案:最小够用版
如果你已经有 Nginx,只想快速把两台 Ollama 实例挂起来,这样配就够用了:
nginx
upstream ollama_backend {
server 127.0.0.1:11434 max_fails=3 fail_timeout=10s;
server 127.0.0.1:11435 max_fails=3 fail_timeout=10s;
keepalive 32;
}
server {
listen 8080;
server_name _;
location / {
proxy_pass http://ollama_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Connection "";
proxy_connect_timeout 3s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
}
这套配置的含义是:把两个 Ollama 实例放进一个 upstream 组,由 Nginx 在它们之间分流;如果某个后端在 fail_timeout 期间连续失败达到 max_fails 次,就会被临时视为失败节点。Nginx 官方文档对 upstream、max_fails、fail_timeout 的语义都有明确说明。(Nginx)
这里有个实战建议:Nginx 适合"已经有它"的环境,不适合把所有高级健康检查幻想都压在它身上。 如果你只是想快速把两个 Ollama 实例挂成一个统一入口,它很方便;但如果你要更明确的主动健康检查、熔断、重试与状态可视化,HAProxy 往往更省心。这个判断基于两者官方文档展示的能力边界。(Nginx)
七、HAProxy 方案:更像生产级标准答案
如果让我给"双 A100 + 双实例 Ollama"选一个更顺手的生产入口,我会更偏向 HAProxy。原因不是它"更高级",而是它对后端健康检查和负载均衡这件事更聚焦。
一个很实用的配置可以这样写:
haproxy
global
log /dev/log local0
maxconn 10000
defaults
mode http
log global
option httplog
timeout connect 5s
timeout client 300s
timeout server 300s
retries 2
frontend ollama_front
bind *:8080
default_backend ollama_back
backend ollama_back
balance roundrobin
option httpchk GET /api/version
http-check expect status 200
server gpu0 127.0.0.1:11434 check inter 3s fall 2 rise 2
server gpu1 127.0.0.1:11435 check inter 3s fall 2 rise 2
这里最关键的是两点:
第一,balance roundrobin 会让请求轮流打到 gpu0 和 gpu1。HAProxy 官方文档写得很明确,后端默认就是共享工作负载,且支持 roundrobin、leastconn 等算法。(HAProxy Technologies)
第二,check 会对后端做健康检查;而 option httpchk GET /api/version 则把健康检查请求定向到 Ollama 的 GET /api/version 接口。HAProxy 官方文档对健康检查的定义非常清晰:不健康的服务器会被移出负载均衡轮转。(HAProxy Technologies)
如果你问我,为什么不是拿 /api/generate 来做健康检查?因为那太重了。/api/version 返回轻、快,而且足够验证进程是否正常提供 HTTP API;真正的"模型是否已预热",可以通过独立预热脚本和 /api/ps 来辅助判断。(Ollama 文档)
八、健康检查应该检查什么,别一上来就查"最重的接口"
这是很多推理服务最容易配错的地方。
第一层:进程存活
检查 /api/version 即可。它轻量、简单、返回快,适合高频探测。官方 API 提供该接口。(Ollama 文档)
第二层:模型是否已装载
检查 /api/ps。它能返回"当前已加载到内存的模型列表",适合用于预热完成后的巡检。官方 API 里就有这个接口。(Ollama 文档)
第三层:推理链路是否真的可用
这层不要用高频健康检查做,而应该用低频巡检或业务级探测,比如每隔一段时间打一条极短的 /api/generate 请求,验证模型能否在可接受时延内返回。因为 /api/generate 返回里自带 total_duration、load_duration、eval_duration 等指标,非常适合用于"慢病监控",但不适合每秒钟高频打。(Ollama 文档)
一句话总结就是:
- 存活用
/api/version - 装载用
/api/ps - 性能用
/api/generate指标
这样既不会把健康检查本身搞成负担,也能把实例状态看得比较完整。相关接口能力都来自官方 API 文档。(Ollama 文档)
九、如何判断"是不是已经把双卡真的吃起来了"
只看到两个服务 running 还不够。你要确认的是:
- 两个实例都在接流量
- 两个实例都装了模型
- 模型没有明显 offload 到 CPU
- 每个请求的负载分布基本均衡
先查各自的模型装载情况:
bash
curl http://127.0.0.1:11434/api/ps
curl http://127.0.0.1:11435/api/ps
然后用 ollama ps 看本机当前模型状态。Ollama 的上下文长度文档明确建议:为了更好的性能,应避免把模型 offload 到 CPU,并通过 ollama ps 里的 PROCESSOR 字段确认是否为 100% GPU。(Ollama 文档)
再配合 nvidia-smi 观察两张卡的显存与利用率:
bash
watch -n 1 nvidia-smi
如果轮询正常、预热正常、请求量足够,你会看到两张卡都在持续吃显存和算力,而不是只有一张卡承担绝大多数请求。
十、调用侧别写死单端口,做一个最朴素的客户端分流就够了
很多团队其实不一定非要上 Nginx 或 HAProxy。你完全可以在调用侧自己做一个非常朴素的分流器:比如轮询 11434 和 11435,失败时自动切到另一边。这种方式的优点是轻量,缺点是健康检查和摘除逻辑要自己写。
最简单的 Python 伪代码大概是这样:
python
import itertools
import requests
ENDPOINTS = itertools.cycle([
"http://127.0.0.1:11434",
"http://127.0.0.1:11435",
])
def generate(prompt, model="gemma3"):
last_error = None
for _ in range(2):
base = next(ENDPOINTS)
try:
r = requests.post(
f"{base}/api/generate",
json={"model": model, "prompt": prompt, "stream": False},
timeout=300,
)
r.raise_for_status()
return r.json()
except Exception as e:
last_error = e
raise last_error
这样做的底层依据是:Ollama 运行后 API 自动可用,而 /api/generate 是标准生成接口;如果你更偏向兼容现有 SDK,Ollama 还提供 OpenAI 兼容接口,base_url 指向 http://localhost:11434/v1 即可。(Ollama 文档)
如果你已经有一套 OpenAI SDK 代码,那么接入双实例时,可以把 "目标 base_url" 做成可切换配置;如果你前面挂了 HAProxy,那业务端甚至不用感知后端有两台 Ollama。Ollama 的 OpenAI 兼容能力来自官方文档。(Ollama 文档)
十一、别盲目把上下文拉大,吞吐优先和长上下文优先是两回事
很多人想"性能拉满",第一反应就是把 OLLAMA_CONTEXT_LENGTH 拉到很大。这个思路并不总对。Ollama 的上下文长度文档强调:为了获得更好性能,应尽量使用模型支持的合适上下文长度并避免 CPU offload;同时,也建议通过 ollama ps 检查实际分配的上下文和处理器分布。(Ollama 文档)
对双实例吞吐优先场景来说,8192 或 16384 这类更克制的上下文 往往更合理。因为你追求的是"单位时间处理更多请求",不是"单请求尽量塞进最长历史"。如果你的业务真的是 Agent、长文分析、超长代码理解,再单独给这类服务起长上下文实例会更科学。这个结论是结合官方对上下文与性能关系的建议做出的工程化取舍。(Ollama 文档)
十二、如何做性能观测,不要只盯着显卡利用率
显卡利用率只是结果,不是全貌。真正有用的观测应该至少包括三类:
1)API 返回指标
/api/generate 的响应里有 total_duration、load_duration、prompt_eval_count、prompt_eval_duration、eval_count、eval_duration。这些字段能告诉你:到底慢在装载、慢在提示词处理,还是慢在生成本身。(Ollama 文档)
2)模型装载状态
/api/ps 能看到当前实例内有哪些模型在内存里。这个接口特别适合判断预热是不是掉了、实例是不是偷偷卸载了模型。(Ollama 文档)
3)实例可达性与版本
/api/version 是最轻量的可达性接口,很适合接到反向代理健康检查,也适合外部巡检脚本快速探测。(Ollama 文档)
所以,别再只看 nvidia-smi。真正能帮你定位瓶颈的,是"API 时延 + 模型装载 + GPU 状态"三件套。
十三、一个很实用的预热与巡检脚本
你可以在服务启动后跑一遍下面这种逻辑:
bash
#!/usr/bin/env bash
MODEL="gemma3"
for port in 11434 11435; do
echo "check version on ${port}"
curl -sf "http://127.0.0.1:${port}/api/version" || exit 1
echo "preload ${MODEL} on ${port}"
curl -sf "http://127.0.0.1:${port}/api/generate" \
-d "{\"model\":\"${MODEL}\"}" > /dev/null || exit 1
echo "check running models on ${port}"
curl -sf "http://127.0.0.1:${port}/api/ps"
done
这个脚本背后的逻辑完全来自官方接口能力:
/api/version做轻探活- 空的
/api/generate请求做预热 /api/ps看是否已装载完成 (Ollama 文档)
如果你用 systemd 管理两个 Ollama 服务,还可以把这个脚本挂到服务启动后的初始化流程里,确保实例在被接流量前就已经热起来。
十四、最终建议:两种常见生产打法
打法一:简单稳定型
- 双实例:
11434、11435 - Nginx 做统一入口
- 被动失败摘除
- 启动后预热
- 业务端只认一个入口
这种方案部署快,改造少,适合已经有 Nginx 的环境。Nginx 的 upstream 和失败摘除语义来自官方文档。(Nginx)
打法二:更像专业负载均衡型
- 双实例:
11434、11435 - HAProxy 做统一入口
httpchk GET /api/versionroundrobin或leastconn- 独立预热脚本
- 业务端只认 HAProxy 入口
这种方案更适合你明确在做 API 服务,且希望健康检查、摘除和分流行为更可控。HAProxy 的健康检查与后端算法能力来自官方文档。(HAProxy Technologies)
如果让我只给一个结论,我会说:
双卡 A100 上要把 Ollama 吞吐跑满,最关键的不是"把参数调多猛",而是"让两个实例持续稳定地接到差不多的流量"。
十五、结尾
到了这一步,双卡 A100 的部署才算真正从"会启动"进入"能打仗"。
前面的安装、权限、端口冲突,解决的是"服务起来";这一篇的轮询、健康检查、预热、性能观测,解决的是"服务跑稳"。只有这两部分合在一起,你的双卡机器才不是一个昂贵的摆设,而是一台真正可持续输出吞吐的推理节点。Ollama 的本地 API、预热方式、运行模型查询、性能指标、OpenAI 兼容接口,再加上 Nginx 或 HAProxy 的分发与健康检查能力,已经足够支撑一套很实用的本地生产方案。(Ollama 文档)