一、先立一个正确的调优目标:你要的是吞吐,不是参数好看

做本地推理服务时,很多人调优的目标其实并不清晰。

有的人想要的是:

  • 单请求尽可能快
  • 首 token 尽量早点出来

有的人想要的是:

  • 单位时间内处理尽可能多的请求
  • 多用户同时进来时整体不抖

这两种目标,不是同一个方向。

如果你要的是最低单请求延迟,那很多时候更适合:

  • 低并发
  • 短上下文
  • 模型常驻
  • 少排队

如果你要的是最大整体吞吐,那才需要开始考虑:

  • 适度提高 OLLAMA_NUM_PARALLEL
  • 把两张卡都吃起来
  • 合理放大队列
  • 控制上下文长度,不让显存被无意义占满

而你的场景,前面已经定得很清楚了:
双卡 A100,双实例部署,目标是把整机吞吐压出来。

那调优的核心就一句话:

不是让一个请求快到极致,而是让每个实例在高并发时都处于“持续高效输出”的状态。

Ollama 官方关于多 GPU 的说明也恰好支持这个思路:如果模型能够完整放进任意一张 GPU,Ollama 会优先把模型加载到单张 GPU 上,因为这样通常性能更好,能减少推理时跨 PCIe 总线的数据传输;只有当模型单卡放不下时,才会分布到所有可用 GPU。也正因为这个机制,在很多能单卡放下模型的场景里,双实例双端口分流 往往比“单实例看见两张卡”更适合做吞吐优先的服务。(Ollama 文档)

二、先看最关键的核心公式:并发和上下文不是叠加关系,而是乘法关系

这可能是整篇文章最重要的一句:

OLLAMA_NUM_PARALLELOLLAMA_CONTEXT_LENGTH 不是简单相加,而是一起放大内存需求。

官方 FAQ 写得非常直接:
并行请求会让上下文大小按并发数放大,例如 2K context + 4 个 parallel request = 8K context,额外的内存分配也会随之增长;所需 RAM/VRAM 会按 OLLAMA_NUM_PARALLEL * OLLAMA_CONTEXT_LENGTH 线性放大。(Ollama 文档)

这句话带来的实战含义非常大。

例如你当前每个实例配置的是:

  • OLLAMA_NUM_PARALLEL=4
  • OLLAMA_CONTEXT_LENGTH=8192

那从内存占用角度看,这个实例并不是“8192 上下文,支持 4 并发”这么简单,而是等价于在高并发时准备了承受 4 × 8192 的上下文压力。(Ollama 文档)

所以你会发现一个很反直觉的现象:

  • 有的人把 parallel 拉高,吞吐反而下降
  • 有的人把上下文抬高,显卡反而更忙但整体更慢
  • 有的人明明双 A100,很快就开始排队

根因往往不是卡不够强,而是上下文和并发一起把显存预算吃爆了。

这也是为什么很多调优失败的本质,不是“参数不够大”,而是“参数组合不对”。

三、OLLAMA_NUM_PARALLEL 不是越大越好,它控制的是单模型的并发承载能力

官方 FAQ 对 OLLAMA_NUM_PARALLEL 的定义很清楚:
它表示每个模型同时处理的最大并行请求数,默认值为 1。(Ollama 文档)

这意味着它解决的是“单个实例能同时扛多少请求”的问题,而不是“服务是不是会更聪明”。

在双实例场景下,你其实有两层并发能力:

第一层,是实例级并发
也就是你有两个端口:

  • 11434
  • 11435

两个实例本身就在并行工作。

第二层,是实例内部并发
也就是每个实例内部由 OLLAMA_NUM_PARALLEL 决定,它能不能同时处理多个请求。(Ollama 文档)

所以你调这个参数时,应该遵循一个非常实用的原则:

1)吞吐优先的起步值:2 或 4

如果你的模型已经常驻、上下文控制得比较克制、业务请求也偏中短文本,那 24 往往是很合理的起点。
这不是官方规定值,而是基于官方给出的“并发 × 上下文放大内存需求”规则,做出的工程化起始点。(Ollama 文档)

2)不建议一上来就 8

因为只要你同时把上下文长度也设得不低,显存压力会迅速抬高。
一旦显存紧张,Ollama 的表现就不会是“优雅变慢”,而可能是:

  • 请求排队
  • 模型装载竞争
  • 503 overloaded
  • 实例间时延抖动变大

官方 FAQ 已经明确说明:请求过多时会返回 503 overloaded,而 OLLAMA_MAX_QUEUE 只是决定最多排多少请求,不会凭空增加吞吐。(Ollama 文档)

所以,调 OLLAMA_NUM_PARALLEL 最忌讳的,不是保守,而是不知道显存预算已经被上下文吃掉了多少。

四、上下文长度不是“能开多大就开多大”,而是要看你服务的任务类型

很多人一看到自己的卡很大,就忍不住想把 OLLAMA_CONTEXT_LENGTH 往上拉。

Ollama 官方上下文长度文档确实写了默认规则:

  • < 24 GiB VRAM:默认 4k
  • 24–48 GiB VRAM:默认 32k
  • >= 48 GiB VRAM:默认 256k

同时,官方也说明,像 web search、agents、coding tools 这类需要大上下文的任务,建议至少设置到 64000 tokens。(Ollama 文档)

很多人看到这里就会下意识得出结论:

我是 A100,大卡,默认都能到 256k,那我就该开大。

但这其实只说对了一半。

因为官方同一页也明确提醒:

  • 更大的上下文会增加运行模型所需的内存
  • 最佳性能应尽量避免把模型 offload 到 CPU
  • 可以通过 ollama ps 查看 PROCESSORCONTEXT,确认是不是 100% GPU、当前实际上下文是多少(Ollama 文档)

这意味着什么?

意味着“能开大”和“适合服务场景”不是一回事。

什么时候适合开大上下文?

如果你的业务是:

  • 长文总结
  • 多轮 Agent
  • 长代码分析
  • 大段 RAG 拼接结果后再推理

那大上下文有价值。

什么时候反而不该开大?

如果你的业务是:

  • 普通问答
  • 短文本生成
  • 中等长度 RAG 最终回答
  • API 服务型推理

那过大的上下文很多时候只是在浪费显存预算,压缩实例可承载的并发空间。

所以对双 A100 双实例吞吐优先场景,我更建议这样理解上下文:

它不是“越大越好”的能力参数,而是“你愿意为单请求记忆窗口支付多少显存成本”的预算参数。

如果你当前目标是吞吐,819216384 这种更克制的范围,往往比一上来就上 65536 更现实。这个结论不是官方直接给出的固定数字,而是基于官方对上下文默认值、显存消耗与 CPU offload 风险的说明做出的工程收敛。(Ollama 文档)

五、OLLAMA_KEEP_ALIVE 决定的不是“快不快”,而是“会不会反复冷启动”

Ollama 官方 FAQ 对模型驻留策略讲得非常清楚:

  • 默认模型会在内存中保留 5 分钟
  • API 请求里可以通过 keep_alive 控制模型停留时间
  • 0 表示响应后立即卸载
  • 任何负数都表示一直保留在内存中
  • 服务级也可以通过 OLLAMA_KEEP_ALIVE 设置默认行为(Ollama 文档)

这意味着,keep_alive 本质上解决的是一个非常现实的问题:

你要不要让下一批请求继续复用已经加载好的模型。

在吞吐优先场景下,这几乎不用犹豫:

  • 需要
  • 非常需要
  • 最好长期保持

因为如果你不这么做,就会出现一个非常讨厌的现象:

  • 前几秒没流量,模型被卸掉
  • 下一波流量一来,又要重新加载
  • 压测结果里 load_duration 时高时低
  • 用户感觉系统“有时快有时慢”

而官方 API Usage 文档明确告诉你,响应里有 load_duration 字段,它表示模型加载耗时。如果这个值在压测中反复冒高,通常就说明模型没有稳定常驻,或者刚刚经历了重新加载。(Ollama 文档)

所以在双实例生产服务里,keep_alive=-1OLLAMA_KEEP_ALIVE=-1 基本可以视为吞吐优先配置的标配。

六、OLLAMA_FLASH_ATTENTIONOLLAMA_KV_CACHE_TYPE 的真正价值,在于“给并发和上下文让路”

这两个参数很多人知道名字,但不知道它们为什么重要。

官方 FAQ 说得很清楚:

  • OLLAMA_FLASH_ATTENTION=1 可以显著降低上下文变大时的内存使用
  • OLLAMA_KV_CACHE_TYPE 默认是 f16
  • q8_0 大约只占 f16 一半的内存,而且通常几乎没有明显质量损失
  • q4_0 更省,但精度损失会更明显(Ollama 文档)

这两条放在一起看,就会得到一个非常实战的结论:

Flash Attention 和 q8_0,不是为了“看起来高级”,而是为了把更多显存预算腾出来给并发和上下文。

换句话说:

  • 你不是因为想炫技才开它
  • 而是因为你想让每个实例在高并发时更稳

尤其在双实例场景里,这种价值更明显。因为你每个实例都要维持一份模型、一份上下文缓存、一套并发处理能力,显存预算本来就很紧张。
这时候:

  • Flash Attention 帮你压缩上下文增长带来的内存压力
  • q8_0 帮你把 KV cache 成本进一步降下来(Ollama 文档)

所以我对这两个参数的建议非常明确:

吞吐优先推荐组合

OLLAMA_FLASH_ATTENTION=1
OLLAMA_KV_CACHE_TYPE=q8_0

这几乎可以视为双卡服务型部署的默认起点。

七、OLLAMA_MAX_LOADED_MODELSOLLAMA_MAX_QUEUE,一个控制显存竞争,一个控制高峰缓冲

这两个参数常常被忽略,但在生产里都很关键。

1)OLLAMA_MAX_LOADED_MODELS

官方 FAQ 说明,它控制同时可加载的模型数量,默认是 3 × GPU 数量
如果有足够内存,多个模型可以同时保持加载;如果内存不够,新的请求会排队,旧模型空闲后可能被卸载以腾出空间。尤其在 GPU 推理场景下,新模型必须能够完整装进 VRAM,才允许并发加载。(Ollama 文档)

对双 A100 的双实例固定模型服务来说,这条规则给出的最佳实践其实很清楚:

如果你每个实例就是为了长期服务一个主模型,那就把它压到 1。

因为你根本不需要一个实例在繁忙时还去尝试同时装多个模型,徒增显存竞争和加载抖动。

2)OLLAMA_MAX_QUEUE

官方 FAQ 说明,它控制服务繁忙时最多排队多少请求,默认是 512;超过这个值,Ollama 会返回 503 overloaded。(Ollama 文档)

这意味着它的作用不是“提高性能”,而是:

给服务高峰提供一个缓冲区。

调这个参数时要有一个很清醒的认识:

  • 它不能创造吞吐
  • 它只能延后拒绝
  • 队列过大可能会把“快速失败”变成“长时间等待后失败”

所以我的建议是:

  • 中小规模服务:可以先保留默认或略增
  • 双 A100 API 服务:1024 往往是一个还算稳妥的缓冲值
  • 再往上加之前,先看业务能不能接受排队等待时间

八、真正会看压测结果的人,不是只看总耗时,而是会拆 Ollama 返回的指标

Ollama 官方 API Usage 文档已经给了你非常宝贵的一组指标:

  • total_duration
  • load_duration
  • prompt_eval_count
  • prompt_eval_duration
  • eval_count
  • eval_duration

并且所有时间值的单位都是纳秒。这些字段在非流式 /api/generate/api/chat 响应里都能拿到,流式响应则会在最后一个 done=true 的 chunk 里给出。(Ollama 文档)

这几项指标的意义非常大。

1)total_duration

它表示整次响应的总耗时。
这是最直观的总体指标,但单独看它,其实帮助有限,因为你不知道慢在了哪里。(Ollama 文档)

2)load_duration

它表示模型加载耗时。
如果你已经做了常驻和预热,这个值理应很低;如果它经常偏高,说明模型在重复加载,或者实例状态不稳定。(Ollama 文档)

3)prompt_eval_count / prompt_eval_duration

它们描述的是输入提示词处理阶段:

  • 输入 token 有多少
  • 处理这些输入花了多久(Ollama 文档)

如果这里的耗时很高,通常说明你的输入太长了,或者上下文过大导致提示词评估阶段变慢。

4)eval_count / eval_duration

它们描述的是生成输出阶段:

  • 生成了多少 token
  • 生成这些 token 花了多久(Ollama 文档)

如果这里很高,则更偏向“生成阶段慢”,而不是装载或提示词处理阶段的问题。

真正的调优,应该是看这几项指标之间的组合,而不是只盯着一个总时间。

九、给你一套最实用的“压测结果判读法”

情况一:load_duration 高,后续明显下降

这通常说明模型已经在变热,是正常现象。
你要看的不是第一次慢不慢,而是:

  • 后续是否稳定变低
  • 两个实例是否都一样
  • /api/ps 是否显示模型已经常驻

只要后续稳定,说明预热和 keep_alive 在发挥作用。(Ollama 文档)

情况二:prompt_eval_duration 很高

这通常说明问题不在模型加载,而在输入本身。
常见原因是:

  • RAG 拼接内容过长
  • 聊天历史过多
  • OLLAMA_CONTEXT_LENGTH 设置得过高,导致提示词处理阶段负担变重

因为上下文越大,需要消耗的内存越多,而并发还会把这种成本继续乘上去。(Ollama 文档)

情况三:eval_duration 高,但 prompt_eval_duration 不高

这表示生成阶段本身更慢,可能和:

  • 输出 token 太多
  • 模型本身较重
  • 当前实例繁忙程度较高

更相关。(Ollama 文档)

情况四:开始出现 503 overloaded

这说明你已经把服务推到它当前承载边界了。
这时应该优先做的是:

  • 降低单实例并发压力
  • 检查是否所有请求都均匀分发到两个实例
  • 看上下文是不是设得过大
  • 看队列是否过长导致请求在服务里堆积

而不是只会继续把 OLLAMA_MAX_QUEUE 往上抬。官方已经说明 503 是队列和忙碌状态触发的过载返回。(Ollama 文档)

十、ollama ps 不是可有可无,它是你判断“有没有真吃满 GPU”的关键观察口

官方上下文长度文档专门强调了一点:

为了获得最佳性能,应尽量避免把模型 offload 到 CPU,并可以通过 ollama ps 查看 PROCESSOR 字段,确认是否是 100% GPU;同时也能看到当前分配的 CONTEXT。(Ollama 文档)

这意味着,调优时你不该只看:

  • nvidia-smi
  • API 耗时

你还应该看:

ollama ps

如果这里显示的不是 100% GPU,那说明你的配置可能已经把模型推到了不够理想的状态,例如:

  • 上下文过大
  • 模型过重
  • 显存不够
  • 参数组合导致 offload

而一旦开始 offload 到 CPU,吞吐表现通常就不会漂亮了。(Ollama 文档)

所以,真正完整的调优观察应该是三件套:

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

十一、给一套适合双 A100 双实例的参数收敛思路

如果你的目标就是吞吐优先的 API 服务,我建议不要一开始就四处乱试,而是按下面顺序收敛。

第一步:先定保守稳定版

OLLAMA_KEEP_ALIVE=-1
OLLAMA_FLASH_ATTENTION=1
OLLAMA_KV_CACHE_TYPE=q8_0
OLLAMA_MAX_LOADED_MODELS=1
OLLAMA_NUM_PARALLEL=2
OLLAMA_MAX_QUEUE=512
OLLAMA_CONTEXT_LENGTH=8192

这套配置的特点是:

  • 模型常驻
  • 缓存省内存
  • 并发不激进
  • 上下文控制在吞吐友好范围

第二步:压测并看指标

重点看:

  • load_duration 是否趋于稳定
  • prompt_eval_duration 是否过高
  • 是否出现 503
  • ollama ps 是否保持 100% GPU(Ollama 文档)

第三步:先加并发,再考虑加上下文

如果目前稳定且 GPU 仍有余力,可以尝试:

OLLAMA_NUM_PARALLEL=4

而不是直接先把上下文抬高。
因为对吞吐服务来说,并发通常比大上下文更直接地转化为“每秒处理请求数”。这个判断基于官方对并发和上下文乘法放大内存需求的说明。(Ollama 文档)

第四步:只有业务真的需要,才去提高上下文长度

例如从:

OLLAMA_CONTEXT_LENGTH=8192

提到:

OLLAMA_CONTEXT_LENGTH=16384

而不是直接冲 65536
因为一旦上下文翻倍,它对并发的成本也会一起翻倍。(Ollama 文档)

十二、最后给你一句最实战的结论

如果你把这篇文章只看成“又多了几个环境变量”,那意义不大。

真正重要的是理解下面这组关系:

  • OLLAMA_NUM_PARALLEL 决定单实例同时吃多少请求
  • OLLAMA_CONTEXT_LENGTH 决定单请求愿意占多少记忆窗口
  • 两者相乘,决定实例的内存压力上限
  • keep_alive 决定模型会不会反复冷启动
  • Flash Attention 和 q8_0 决定你能不能把更多显存腾给并发和上下文
  • /api/generate 返回指标决定你能不能看懂“慢在哪里”
  • ollama ps 决定你能不能确认模型是否仍在 100% GPU 状态(Ollama 文档)

说得更直白一点:

吞吐调优不是调参数,而是在做显存预算分配。

你在分配的是:

  • 给并发多少预算
  • 给上下文多少预算
  • 给模型常驻多少预算
  • 给高峰排队多少预算

当你把这件事看明白,双卡 A100 上的 Ollama 才真正从“能跑”进化成“能打”。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐