num_parallel让多个请求共享模型权重实现真正并发,batch生成可显著提升总体tokens/秒,而非简单加载模型副本。
提高 num_parallel 并不会把模型加载两次。它把一个已加载的模型划分为多个请求槽位,这些槽位共享权重但不共享缓存——这就是为什么内存占用确实存在、但并不与模型大小成正比,也是为什么这个算法规的结果在两个方向上都让人意外。
Ollama 启动 runner 时会传入两个相关参数:一个总 context 大小,以及一个并行槽位数。llama.cpp server 将其 context 分配均匀划分给各个槽位,每个飞行中的请求占用一个槽位。权重只加载一次、每个槽位都能读取,所以第二个并发请求不会产生额外的模型副本。
这也是一个吞吐量机制,而不仅仅是容量机制。为一个请求生成一个 token 需要从内存读取整个活动权重集来做非常少量的计算,这在计算层上几乎让 GPU 完全闲置。从一次权重读取中服务多个序列,这就是批处理的收益:跨所有请求的每秒总 token 数远高于单个流所能达到的水平,尽管单个流本身可能会稍微变慢。
精确的关系可以从 Ollama 传给 runner 的参数中看到:context flag 被设置为 num_ctx x num_parallel,槽位计数被设置为 num_parallel。Ollama 的 FAQ 从另一个角度说明了同样的事情——所需内存按 OLLAMA_NUM_PARALLEL 乘以 OLLAMA_CONTEXT_LENGTH 的 O 量级增长——并给出了一个计算案例:2K context 配合 4 个并行请求,结果是 8K context 和额外的内存分配。
所以每个槽位获得一个大小为 num_ctx 的私有 KV 缓存,总缓存大小是二者的乘积。根据 num_ctx 页面推导的每 token 数据,计算 Qwen3-8B 在 f16 下的情况——每 token 144 KiB,来自 36 层、8 个 key-value 头、每个头维度 128:
num_ctx 8,192, num_parallel 1 -> 8,192 x 144 KiB = 1.13 GiB
num_ctx 8,192, num_parallel 4 -> 32,768 x 144 KiB = 4.50 GiB
num_ctx 8,192, num_parallel 8 -> 65,536 x 144 KiB = 9.00 GiB
weights (q4_K_M) ~ 4.87 GiB
在一张 8 GB 显存的卡上,该模型配四个槽位,加上权重后的需求就已经超过卡的实际容量了,此时加载会溢出到系统 RAM 而不是直接拒绝。这是一种性能崩塌而非报错,这也正是它难以被归因的原因。
有一个后果会让那些根本没打算提供并发服务的人中招:乘法发生在加载时,而不是第二个请求到达时。用四个槽位启动的 runner 在第一个请求完成前就已经分配了四个缓存,所以在一台只有单个用户的机器上提高这个设置,白白浪费了四分之三的缓存预算。这与"并发设置在没用上之前是免费的"这一直觉正好相反。
另一个相互作用的因素是自动 context 调整。如果你没有在任何地方设置 context,Ollama 会从检测到的 VRAM 中选择一个,但这个选择不考虑 num_parallel——乘法是在之后应用的。因此在一张 32k 档次的卡上,四个槽位需要 128k 的缓存。这恰恰就是那种在空闲机器上加载顺利、一旦真正使用就溢出的配置,也是为什么只要你设置了槽位,就一定要显式设置 num_ctx 的原因。
换个角度,这个设置回答的是一个预算问题:
kv_budget = free_vram - weights - overhead
tokens_total = kv_budget / bytes_per_token
num_ctx = tokens_total / num_parallel
这就让权衡变得显式了。固定的缓存预算可以买一条长对话,也可以买若干条短对话,而乘积是固定的。四个 8k 槽位和一个 32k 槽位占用相同的内存。这是一个需要主动做出的决策:编码助手需要长的单个 context,API 服务多个短分类调用则需要槽位。
当前的默认值在 Ollama 配置源码中是 1,FAQ 也有相同记录。这个默认值并非一直如此——早期版本会从可用内存中自动选择 4 或 1——所以某篇描述自动选择的指南描述的是与你正在运行的版本不同的版本。用 ollama serve --help 确认,它会打印出环境变量及其实际值。
某些模型族被强制为单槽位,无论设置如何。Ollama 的调度器包含一个明确罗列的架构列表,这些架构被固定为单个并行请求,主要是那些状态无法以相同方式批处理的多模态和循环注意力模型族。如果你的 num_parallel 对某个模型没有效果但对另一个模型有效,原因很可能就在这里。
超出槽位数量的请求会如何处理
超过槽位数量的请求会被排队而不是拒绝,直到 OLLAMA_MAX_QUEUE,Ollama 的 FAQ 记录其默认值是 512。超过这个数量后服务器返回 HTTP 503 表示过载。排队的请求生成并不慢;只是启动慢,表现为首个 token 的生成时间(time-to-first-token)上升,而每个 token 的速率保持平坦。这两个数字背离是排队的标志性特征,而不是模型慢的标志,把它们平均成一个延迟数字会完全掩盖这个问题。
同时加载多个不同的模型是另一个维度,由 OLLAMA_MAX_LOADED_MODELS 控制——文档记录默认为每个 GPU 三个——它需要每个模型权重的完整副本,而不是共享权重。这个话题在"同时运行多个模型"中有详细讨论。
一人一机。 保持在 1。不使用的槽位仍然会分割 context 分配。
小团队或内部服务。 两到四个槽位,同时降低 num_ctx 以保持乘积在 VRAM 之内,通常比一个槽位加排队获得更好的感知响应速度。
无人等待的批量工作。 槽位在这里能带来真正的吞吐量,因为如果整个批次能更快完成,没人会在意每个条目稍微慢一点。
做任何改动之后。 之后用 ollama ps 确认模型仍然报告 100% GPU利用率。如果某个设置把模型推到了系统 RAM,看起来会像是全方位的退化,会被归咎于任何其他原因而不是槽位数量。
Ollama's num_ctx Parameter and What It Actually Sets
Running Multiple Models at Once in Ollama
Ollama's keep_alive Setting and Model Unloading