SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor

Ollama 并发配置:OLLAMA_NUM_PARALLEL 与 MAX_QUEUE 详解

深入解析 Ollama 并发机制。当请求超过 OLLAMA_NUM_PARALLEL 时,系统如何处理排队与 HTTP 503 错误。了解为何增加并行槽位会成倍消耗 VRAM 显存,以及如何通过配置平衡响应速度与硬件负载,避免因 KV 缓存溢出导致的加载失败。

当第一个 Ollama 请求正在生成时,第二个请求会发生什么

Ollama 的并发性由三个环境变量决定,默认情况下,一个已加载的模型一次只能处理一个请求。第二个请求不会被拒绝,也不会收到部分答案。它会在队列中等待,直到有空闲槽位,然后以正常速度运行。

传入的请求有三种可能的结果。它会立即在空闲槽位中启动。它会在队列中等待。或者队列已满,服务器返回 HTTP 503 拒绝请求。具体结果由 OLLAMA_NUM_PARALLELOLLAMA_MAX_QUEUEOLLAMA_MAX_LOADED_MODELS 决定。

默认设置是安全的,这也是为什么第二个用户会报告服务器“挂起”而实际上并未出现故障的原因。增加槽位只需修改两行配置。真正的难点在于内存。每个并行槽位都需要其专属的键值缓存(KV cache),即模型为已处理的 token 保留的内存块。如果在不增加 VRAM(GPU 显存)的情况下增加槽位,会导致原本缓慢的响应变成加载失败。

OLLAMA_NUM_PARALLEL、OLLAMA_MAX_QUEUE 和 OLLAMA_MAX_LOADED_MODELS 的控制范围

以下是截至 2026 年 8 月 Ollama 各版本中的默认值。请勿直接采信此处数值,应通过下文所示的日志行确认你当前环境的实际配置。

  • OLLAMA_NUM_PARALLEL 控制单个已加载模型同时处理的请求数量。默认值为 1,即请求按顺序逐个处理。
  • OLLAMA_MAX_LOADED_MODELS 控制同时驻留在内存中的不同模型数量。默认值为 0,表示由 Ollama 自动决定:每个 GPU 加载 3 个模型,无 GPU 的机器也加载 3 个模型。
  • OLLAMA_MAX_QUEUE 控制允许排队等待的请求数量。默认值为 512。当队列已满时,新到达的请求会被直接拒绝。

最坏情况下的内存占用是前两者的乘积。若同时加载 2 个模型且每个模型分配 4 个槽位,则意味着同时存在 8 个 KV 缓存槽位,Ollama 会尝试满足该资源需求。在单 GPU 服务器上,通常建议仅保留一个模型并为其分配多个槽位,这样计算逻辑更直观且易于管理。

为什么每个并行槽位都会占用 VRAM

当 Ollama 加载模型时,它会启动一个独立的运行进程。其中两个参数至关重要:-c 是运行进程为 KV 缓存分配的总上下文长度,-np 是并行序列的数量。Ollama 将 -c 设置为单次请求的上下文长度乘以槽位数量。随后,运行进程将该总量平均分配给各个槽位,以确保每个请求仍能获得您设定的上下文长度。

这就是全部的限制条件,也是并行处理并非免费的原因。将槽位从 1 个增加到 4 个,意味着在相同的单次请求上下文长度下,需要 4 倍的 KV 缓存。槽位之间不共享任何资源,且空闲槽位的份额也不会借给忙碌的槽位,因为分配比例在运行进程启动时即已固定。

您可以查看实际生效的数值,而非您预想设置的数值:

journalctl -u ollama --no-pager -n 500 | grep "starting llama-server"

该行包含了完整的运行进程命令行,包括 -c-np。如果您设置了变量后 -np 仍为 1,说明该设置未生效,下一节将说明原因。

如果模型权重加上 KV 缓存无法完全装入 VRAM,Ollama 会将部分层移至系统内存,这些层将由 CPU 运行。CPU 层的运行速度远低于 GPU 层,因此这会导致所有请求变慢,包括您最初发起的单个请求。因此,盲目提高并行度反而可能降低吞吐量,而非提升它。

ollama ps

当模型完全装入显存时,PROCESSOR 列会显示 100% GPU。类似 35%/65% CPU/GPU 的拆分结果意味着模型的一部分正在 CPU 上运行。SIZE 列包含了 KV 缓存,因此当您增加槽位数量并重新加载模型时,该数值会增加。调高 OLLAMA_NUM_PARALLEL,重启服务,发送一个请求,然后再次运行 ollama ps:这就是您所做更改带来的内存开销,这是通过测量得出的结果,而非猜测。

上下文长度和槽位数量是相乘关系,因此必须同时考虑。4 个槽位的大上下文相当于 4 个大上下文。如果您同时也在调整 模型的 num_ctx 上下文窗口,请每次只修改其中一个参数,否则您将无法判断是哪一个参数导致显存溢出。

如何设置这些变量以使其在重启后生效

在 Linux 上,Ollama 作为 systemd 服务运行。在 shell 中运行 export OLLAMA_NUM_PARALLEL=4 无法生效,因为 systemd 使用其自身的独立环境启动服务,无法读取 shell 变量。请使用 drop-in 文件进行配置。

sudo systemctl edit ollama.service

在打开的编辑器中添加以下内容:

[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_MAX_LOADED_MODELS=1"
Environment="OLLAMA_MAX_QUEUE=32"

随后重新加载并重启服务:

sudo systemctl daemon-reload
sudo systemctl restart ollama
systemctl show ollama --property=Environment

systemctl show 会打印出 systemd 传递给进程的环境变量。如果其中缺少您的变量,说明 drop-in 文件未保存或未执行 daemon-reload。请同时从服务器端进行确认:

journalctl -u ollama --no-pager | grep "server config" | tail -1

Ollama 会在启动时记录其完整环境,相关日志行的消息为 server config。该映射表是判断变量是否生效的最终依据,也是解决变量配置争议的最快方法。

已加载的模型会保留其启动时的槽位(slot)数量,因为该值在运行进程启动时已固定。上述重启操作会卸载所有模型,因此下一次请求将以新设置重新加载模型,并产生一次加载耗时。模型在此之后驻留内存的时长由另一项设置控制,详见 在请求间保持 Ollama 模型加载

从客户端视角观察已服务、排队和拒绝请求的表现

同时发送多个请求并进行计时。以下命令并行运行 8 个流式请求,并打印每个请求的状态和耗时:

for i in $(seq 1 8); do
  curl -s -o /dev/null \
    -w "req$i http=%{http_code} ttfb=%{time_starttransfer}s total=%{time_total}s\n" \
    http://127.0.0.1:11434/api/generate \
    -d '{"model":"llama3.2:3b","prompt":"Explain what a KV cache is.","stream":true}' &
done
wait

ttfb 是流的首字节响应时间,它非常接近首字延迟(TTFT),因为第一个流式分块中包含了第一个生成的 Token。

并行服务。 每个请求报告的 ttfb 相似,且 total 会随所有请求同步上升。GPU 在运行中的槽位间共享,因此每个回答的生成速度比单独运行时慢,但单位时间内完成的总请求数更多。这就是调高 OLLAMA_NUM_PARALLEL 时所处的运行模式。

排队。 前几个请求响应迅速,后续请求则显示较长的 ttfb,随后是正常的生成过程。等待时间源于队列,而非模型本身。用户在聊天窗口中会看到长时间的空白停顿,随后文本以全速输出。这种“启动慢、生成快”的形态是队列拥塞的典型特征,而非 GPU 过载。

拒绝。 客户端会几乎立即收到 http=503,响应体如下:

{"error":"server busy, please try again.  maximum pending requests exceeded"}

该消息表示请求到达时队列已满。它与 VRAM 或模型状态无关。

一个客观限制:Ollama 不会发布队列深度。ollama ps/api/ps 端点报告的是已加载的模型,而非等待中的请求。因此,你需要从客户端侧测量队列情况,通过观察首字节响应时间,或者在前端代理层统计 503 响应的数量。

为什么较小的 MAX_QUEUE 设置通常更优

512 的队列长度听起来很充裕,但在单槽位处理中几乎毫无用处。第 300 个请求需要等待前 299 个请求全部完成。这至少需要几分钟时间。任何 HTTP 客户端都会在此之前放弃连接,导致调用方遇到客户端超时。这种超时无法反映根本原因,也无法触发监控告警。

将队列长度设置为服务器在客户端超时时间内能够处理的请求量。这样,一旦溢出,系统会立即返回 503 错误。503 状态码非常有用:反向代理可以重试请求,客户端可以执行退避策略,仪表盘可以统计错误,运维人员也能直观发现问题。请根据实际测量结果计算该数值。如果生成一个响应需要 10 秒,而客户端等待时间为 60 秒,则每个槽位在窗口期内大约能处理 6 个请求。此时,如果队列深度远超该数值,只会导致超时。

何时在 Ollama 前端部署队列

内置队列采用先进先出(FIFO)机制,且无法识别调用方身份。对于单应用连接单服务器的场景,这已足够,增加基础设施只会引入额外的故障点。当出现以下情况时,应考虑在前端部署队列:

  • 需要优先级控制。交互式聊天不应排在批量摘要任务之后。Ollama 的队列不支持优先级,因此必须在外部拦截批量任务并缓慢投递。
  • 需要公平性。单个客户端可能占满整个队列,导致其他所有请求收到 503 错误。
  • 需要任务在重启后不丢失。队列驻留在服务器内存中。重启 Ollama 会导致所有等待中的请求丢失。
  • 需要具备退避机制的可靠重试,并记录在案以便后续审计。

轻量级方案是使用反向代理。在 nginx 中,limit_conn 可限制并发连接数,limit_req 可限制每个客户端的请求速率,从而在代理层拒绝溢出流量,使其不会触及 Ollama 的队列。重量级方案是在调用 Ollama 的 worker 前部署一个带有数据库的任务队列,当请求必须在进程重启后依然存活时,这是必要的选择。针对实际流量进行容量规划是一项专门的工作:规划支持并发用户的自托管 LLM 详细介绍了相关计算方法,而 在 VPS 上运行 Ollama 则涵盖了这些变量所依赖的基础安装配置。

当诚实的答案是更换服务器时

有些限制无法通过调整参数来突破。Ollama 在加载模型时会将 KV 缓存拆分为大小相等且固定的槽位。空闲槽位的内存无法被繁忙的槽位使用,且在不卸载模型的情况下无法更改槽位数量。这种设计非常适合个人、小团队或编码代理使用。

为大量并发用户构建的服务器工作方式不同。它们按需以小页面的形式分配 KV 缓存,并将新到达的请求添加到正在运行的批处理中,因此内存分配遵循实际需求,而非固定的划分方式。如果你的目标是在单张 GPU 上支持大量并发用户,这种架构差异比任何 OLLAMA_NUM_PARALLEL 的数值都更重要。Ollama 与 vLLM 的对比 是做出该决策的参考依据。但不要仅凭原则进行切换:维护不同的服务器会增加运维成本;如果你的流量仅来自少数人,内置的行为就是正确的选择。

测量您自己的吞吐量和首字生成时间

发布的每秒生成 token 数(tokens per second)数据来自他人的 GPU、模型、量化方式、上下文长度和提示词。这些条件与您的环境均不匹配,因此请将读到的任何数字仅视为粗略参考,并测量您面前的机器性能。

Ollama 在每次响应的最终 JSON 对象中返回计时信息。eval_count 是生成的 token 数量,eval_duration 是生成这些 token 所花费的时间,单位为纳秒。

sudo apt install -y jq
curl -s http://127.0.0.1:11434/api/generate \
  -d '{"model":"llama3.2:3b","prompt":"Explain what a KV cache is.","stream":false}' \
  | jq '{prompt_eval_count, eval_count, eval_duration, tokens_per_second: (.eval_count / (.eval_duration / 1000000000))}'

先以单并发运行,再以您预期的并发量运行,并比较决定用户体验的两个关键指标:首字生成时间(time to first token)和每个请求的每秒生成 token 数。随着并发槽位(slots)增加,每个请求的吞吐量必然下降。问题的关键在于下降幅度是否超出了用户的可接受范围。测量本地 LLM 的每秒 token 生成数 一文详细介绍了该方法,包括如何在多次运行之间保持提示词不变。

公开端点配合大容量队列是拒绝服务攻击的目标

设置 OLLAMA_HOST=0.0.0.0:11434 会使 API 监听所有网络接口,而 Ollama 本身不具备身份验证功能。如果开放该端点并使用默认队列,任何发现此接口的人都可以发送 512 个排队请求。攻击者无需登录、无需付费,只需发送长提示词即可轻易填满队列,且几乎没有成本。这会导致您的合法用户收到 503 错误或面临长时间等待,同时服务器资源被完全耗尽。

请将监听地址保持在回环接口(loopback),通过 SSH 隧道或私有网络访问,或者在前端添加身份验证和速率限制。保护 Ollama API 端点 涵盖了这两种方案。完成上述安全配置后再调整队列长度,因为队列长度仅是容量设置,无法提供任何保护作用。

FAQ

为什么我的第二个 Ollama 请求必须等待第一个完成?

因为 OLLAMA_NUM_PARALLEL 默认值为 1,所以已加载的模型一次只能处理一个请求,其余请求按顺序排队。等待中的请求会保持 HTTP 连接打开,在空闲槽位出现前不会发送任何字节,这在客户端看来与模型响应缓慢的表现一致。判断依据是时间特性:如果出现长时间停顿后文本全速输出,说明是在排队;如果从第一个 token 开始就缓慢输出,说明是模型本身运行缓慢。请通过 systemd 的 drop-in 文件增加槽位数量并重启服务。

“server busy, please try again. maximum pending requests exceeded” 是什么意思?

这是 Ollama 的队列溢出错误,返回 HTTP 503 状态码。当前等待的请求数量已达到 OLLAMA_MAX_QUEUE(默认值为 512),因此新请求被拒绝,未进入队列。这不是内存错误,也不是模型错误。单纯增加队列长度只会让调用者在被拒绝前等待更久,真正的解决方法是:如果显存充足则增加槽位,或者减少并发负载,亦或是在前端部署一个具备重试和优先级排序功能的队列。

调大 OLLAMA_NUM_PARALLEL 能让 Ollama 变快吗?

不能。它允许更多请求同时运行,但由于它们共享同一个 GPU,每个请求的速度都会比单独运行时更慢。此外,这会成倍增加 KV 缓存,因为 Ollama 启动运行器时,总上下文大小等于上下文长度乘以槽位数量。如果结果无法放入显存,Ollama 会将层卸载到 CPU,导致所有请求变慢,即使在没有并发的情况下也是如此。修改后请检查 ollama ps,确认 PROCESSOR 列的值仍为 100% GPU

修改这些变量后需要重启 Ollama 吗?

需要。服务器仅在启动时读取这些变量,且正在运行的模型会锁定启动时分配的槽位数量。请使用 sudo systemctl edit ollama.service 编辑 drop-in 文件,然后运行 sudo systemctl daemon-reloadsudo systemctl restart ollama。使用 systemctl show ollama --property=Environment 进行确认,并检查 journalctl -u ollama 中的 server config 行,该行列出了服务器实际加载的环境变量。

我应该设置多少个并行槽位?

从 1 开始,每次增加一个。每调整一次,重启 Ollama,发送一个请求以加载模型,然后运行 ollama ps。在 PROCESSOR 仍显示为 100% GPUSIZE 列为最长上下文预留足够空间的前提下,停在最后一个数值。随后在实际并发环境下测量首字延迟(time to first token)和每秒生成的 token 数,如果单请求速度低于用户可接受的范围,请回退一个档位。

#ollama#concurrency#vram#queueing#self-hosted-llm