双卡 A100 + Ollama 吞吐调优`OLLAMA_NUM_PARALLEL`、上下文长度、KV Cache 与压测结果怎么一起看
一、先立一个正确的调优目标:你要的是吞吐,不是参数好看
做本地推理服务时,很多人调优的目标其实并不清晰。
有的人想要的是:
- 单请求尽可能快
- 首 token 尽量早点出来
有的人想要的是:
- 单位时间内处理尽可能多的请求
- 多用户同时进来时整体不抖
这两种目标,不是同一个方向。
如果你要的是最低单请求延迟,那很多时候更适合:
- 低并发
- 短上下文
- 模型常驻
- 少排队
如果你要的是最大整体吞吐,那才需要开始考虑:
- 适度提高
OLLAMA_NUM_PARALLEL - 把两张卡都吃起来
- 合理放大队列
- 控制上下文长度,不让显存被无意义占满
而你的场景,前面已经定得很清楚了:
双卡 A100,双实例部署,目标是把整机吞吐压出来。
那调优的核心就一句话:
不是让一个请求快到极致,而是让每个实例在高并发时都处于“持续高效输出”的状态。
Ollama 官方关于多 GPU 的说明也恰好支持这个思路:如果模型能够完整放进任意一张 GPU,Ollama 会优先把模型加载到单张 GPU 上,因为这样通常性能更好,能减少推理时跨 PCIe 总线的数据传输;只有当模型单卡放不下时,才会分布到所有可用 GPU。也正因为这个机制,在很多能单卡放下模型的场景里,双实例双端口分流 往往比“单实例看见两张卡”更适合做吞吐优先的服务。(Ollama 文档)
二、先看最关键的核心公式:并发和上下文不是叠加关系,而是乘法关系
这可能是整篇文章最重要的一句:
OLLAMA_NUM_PARALLEL 和 OLLAMA_CONTEXT_LENGTH 不是简单相加,而是一起放大内存需求。
官方 FAQ 写得非常直接:
并行请求会让上下文大小按并发数放大,例如 2K context + 4 个 parallel request = 8K context,额外的内存分配也会随之增长;所需 RAM/VRAM 会按 OLLAMA_NUM_PARALLEL * OLLAMA_CONTEXT_LENGTH 线性放大。(Ollama 文档)
这句话带来的实战含义非常大。
例如你当前每个实例配置的是:
OLLAMA_NUM_PARALLEL=4OLLAMA_CONTEXT_LENGTH=8192
那从内存占用角度看,这个实例并不是“8192 上下文,支持 4 并发”这么简单,而是等价于在高并发时准备了承受 4 × 8192 的上下文压力。(Ollama 文档)
所以你会发现一个很反直觉的现象:
- 有的人把
parallel拉高,吞吐反而下降 - 有的人把上下文抬高,显卡反而更忙但整体更慢
- 有的人明明双 A100,很快就开始排队
根因往往不是卡不够强,而是上下文和并发一起把显存预算吃爆了。
这也是为什么很多调优失败的本质,不是“参数不够大”,而是“参数组合不对”。
三、OLLAMA_NUM_PARALLEL 不是越大越好,它控制的是单模型的并发承载能力
官方 FAQ 对 OLLAMA_NUM_PARALLEL 的定义很清楚:
它表示每个模型同时处理的最大并行请求数,默认值为 1。(Ollama 文档)
这意味着它解决的是“单个实例能同时扛多少请求”的问题,而不是“服务是不是会更聪明”。
在双实例场景下,你其实有两层并发能力:
第一层,是实例级并发。
也就是你有两个端口:
1143411435
两个实例本身就在并行工作。
第二层,是实例内部并发。
也就是每个实例内部由 OLLAMA_NUM_PARALLEL 决定,它能不能同时处理多个请求。(Ollama 文档)
所以你调这个参数时,应该遵循一个非常实用的原则:
1)吞吐优先的起步值:2 或 4
如果你的模型已经常驻、上下文控制得比较克制、业务请求也偏中短文本,那 2 或 4 往往是很合理的起点。
这不是官方规定值,而是基于官方给出的“并发 × 上下文放大内存需求”规则,做出的工程化起始点。(Ollama 文档)
2)不建议一上来就 8
因为只要你同时把上下文长度也设得不低,显存压力会迅速抬高。
一旦显存紧张,Ollama 的表现就不会是“优雅变慢”,而可能是:
- 请求排队
- 模型装载竞争
- 503 overloaded
- 实例间时延抖动变大
官方 FAQ 已经明确说明:请求过多时会返回 503 overloaded,而 OLLAMA_MAX_QUEUE 只是决定最多排多少请求,不会凭空增加吞吐。(Ollama 文档)
所以,调 OLLAMA_NUM_PARALLEL 最忌讳的,不是保守,而是不知道显存预算已经被上下文吃掉了多少。
四、上下文长度不是“能开多大就开多大”,而是要看你服务的任务类型
很多人一看到自己的卡很大,就忍不住想把 OLLAMA_CONTEXT_LENGTH 往上拉。
Ollama 官方上下文长度文档确实写了默认规则:
< 24 GiB VRAM:默认 4k24–48 GiB VRAM:默认 32k>= 48 GiB VRAM:默认 256k
同时,官方也说明,像 web search、agents、coding tools 这类需要大上下文的任务,建议至少设置到 64000 tokens。(Ollama 文档)
很多人看到这里就会下意识得出结论:
我是 A100,大卡,默认都能到 256k,那我就该开大。
但这其实只说对了一半。
因为官方同一页也明确提醒:
- 更大的上下文会增加运行模型所需的内存
- 最佳性能应尽量避免把模型 offload 到 CPU
- 可以通过
ollama ps查看PROCESSOR和CONTEXT,确认是不是100% GPU、当前实际上下文是多少(Ollama 文档)
这意味着什么?
意味着“能开大”和“适合服务场景”不是一回事。
什么时候适合开大上下文?
如果你的业务是:
- 长文总结
- 多轮 Agent
- 长代码分析
- 大段 RAG 拼接结果后再推理
那大上下文有价值。
什么时候反而不该开大?
如果你的业务是:
- 普通问答
- 短文本生成
- 中等长度 RAG 最终回答
- API 服务型推理
那过大的上下文很多时候只是在浪费显存预算,压缩实例可承载的并发空间。
所以对双 A100 双实例吞吐优先场景,我更建议这样理解上下文:
它不是“越大越好”的能力参数,而是“你愿意为单请求记忆窗口支付多少显存成本”的预算参数。
如果你当前目标是吞吐,8192 或 16384 这种更克制的范围,往往比一上来就上 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=-1 或 OLLAMA_KEEP_ALIVE=-1 基本可以视为吞吐优先配置的标配。
六、OLLAMA_FLASH_ATTENTION 和 OLLAMA_KV_CACHE_TYPE 的真正价值,在于“给并发和上下文让路”
这两个参数很多人知道名字,但不知道它们为什么重要。
官方 FAQ 说得很清楚:
OLLAMA_FLASH_ATTENTION=1可以显著降低上下文变大时的内存使用OLLAMA_KV_CACHE_TYPE默认是f16q8_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_MODELS 和 OLLAMA_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_durationload_durationprompt_eval_countprompt_eval_durationeval_counteval_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 psnvidia-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 才真正从“能跑”进化成“能打”。
更多推荐



所有评论(0)