跳到主要内容

推理路由

推理路由(inference routing)指的是决定由哪个 worker 处理每个 LLM 请求的过程。在规模较小时,这个决策可能隐藏在模型服务器或简单的网关内部。随着部署扩展到许多副本和 GPU,它成为推理优化的重要一环。

路由决策会影响诸如以下事情:

  • 请求能否复用现有的 KV 缓存
  • 请求是否会卡在长时间运行的生成之后
  • 目标 worker 是否有足够的空闲内存
  • 预填充或解码工作是否已经让 worker 饱和
  • 在流量模式不均匀时,整个 GPU 池能否保持忙碌

因此,推理路由与前缀缓存KV 缓存卸载预填充-解码分离等概念密切相关。

在平台规模上,路由也是一种经济工具。更好的放置位置能让 GPU 在异构租户和突发工作负载中保持有用,而不只是让单个请求更快。

备注

在本页中,worker 指一个可路由的单元,能够独立运行推理并拥有一些运行时状态,尤其是 KV 缓存。根据部署方式,它可以对应不同的对象:

  • 单个进程或副本。一个 vLLM、SGLang、MAX 或类似的模型服务器进程。
  • Kubernetes Pod。在许多 Kubernetes 部署中,一个模型服务 Pod 就是从路由器角度来看的 worker。
  • 节点。通常与 worker 不是一回事,除非整个节点被当作一个服务副本。一个节点可能托管多个 Pod 或 worker。
  • GPU 组。一个 worker 可能使用一块 GPU、使用张量并行的多块 GPU,或一个完整的多 GPU 副本。

为什么推理的路由与众不同

传统负载均衡器把后端视为相同的黑盒。请求进来,任何后端都能处理,响应不会留下多少有用的状态。当请求短小、无状态且成本相似时,这种模式效果很好。

LLM 推理从多个方面打破了这些假设:

  • 请求并不对等。一个 100-token 的提示词和一个 100k-token 的提示词有着完全不同的内存和计算占用。有些请求在几毫秒内完成,而另一些可能生成数分钟的 token。
  • worker 携带状态。在预填充期间,模型构建的 KV 缓存可以被后续请求复用。然而,只有当下一个请求到达一个已经拥有正确缓存的 worker 时,这种复用才会发生。这对于多轮聊天和智能体工作流尤为重要,因为这些场景的提示词往往共享大量前缀。
  • 预填充和解码压榨不同的资源。预填充主要受计算限制,而解码通常受内存带宽限制。一个正忙于解码长输出的 worker 仍然可以接受新的预填充工作,反之亦然。许多分布式系统会将它们完全分离,以避免浪费 GPU 计算周期和内存带宽。详情见预填充-解码分离
  • 不同工作负载的延迟目标不同。代码补全、聊天应用、智能体和批推理任务都有不同的延迟要求。有些工作负载优先考虑低 TTFT,而另一些更关心吞吐量或总成本。如果每个 worker 都被同等对待,昂贵的长时间运行请求可能会干扰对延迟敏感的请求。例如,一个代码补全请求可能最终排在长智能体生成之后等待,尽管用户期望即时响应。

当路由器看不到这些细节时,它就会开始做出糟糕的决策,导致:

  • 缓存复用被错过。后续请求可能落在没有之前 KV 缓存的 worker 上。这迫使系统重新计算整个前缀,从而增加 TTFT。
  • 延迟增加。请求可能排在长时间解码密集的生成之后,或失去缓存局部性(意味着昂贵的重新计算)。
  • 吞吐量下降。GPU 时间被浪费在重新计算前缀上,而不是服务新工作。
  • 负载不均。一些 worker 因长时间运行的生成而过载,而另一些则大多处于空闲。

对于 LLM 推理,仅仅均匀地分散流量是不够的。更重要的是,路由器应该最小化服务成本,并让整个 worker 池保持高效和响应迅速。

路由策略

LLM 推理路由器可以使用许多信号来决定把请求送到哪里。具体信号取决于服务栈,但最有用的通常来自缓存局部性、worker 负载、内存压力和请求优先级。

轮询路由

轮询路由(round-robin routing)按顺序循环遍历可用的 worker,将请求均匀地分布到整个池中。它简单、可预测,并且作为基线很有用。

当请求短小、提示词很少重复,并且每个 worker 能以大致相同的成本服务任何请求时,它效果最好。

缺点是轮询路由完全对缓存"视而不见"。有 N 个相同的 worker 时,如果没有亲和机制,后续请求落在同一个 worker 上的概率大约只有 1 / N

对于长提示词、多轮聊天和智能体工作负载,这往往导致重复的预填充计算和更高的 TTFT。

随机路由

随机路由(random routing)为每个请求随机选择一个 worker。

与轮询一样,它易于实现,有时也用于测试或基准测试,因为它引入的路由偏差很小。然而,它几乎忽略了所有与推理相关的运行时信息,包括前缀重叠、活跃解码负载和队列深度。

最少负载路由

最少负载路由(least-loaded routing)将新请求发送到活跃请求或连接最少的 worker。

当请求时长差异显著时,这比轮询效果更好。处理多个长生成的 worker 自然会比大多空闲的 worker 收到更少的新流量。当路由器没有可靠的缓存元数据时,这是一个合理的兜底方案。

然而,最少负载路由仍然对缓存"视而不见"。它可能选择一个负载轻但没有有用前缀缓存的 worker,而不是一个负载适中但能跳过大部分预填充的 worker。

直接路由

直接路由(direct routing)显式地指向特定 worker。例如,网关、端点选择器或自定义调度器可能已经知道哪个 worker 拥有某个对话的 KV 缓存。它不再让服务层做另一个路由决策,而是直接将请求转发到那个 worker。

NVIDIA Dynamo 通过指定目标 worker 的路由提示(routing hints)来支持这种模型。

严格来说,它本身并不是一种策略。它是一种执行在别处做出的决策的方式。

感知 KV 缓存利用率的路由

这种策略会考虑每个 worker 的内存中有多少已被 KV 缓存占用。

这很重要,因为即使在原始 GPU 计算利用率看起来适中时,长上下文工作负载也可能遇到内存压力。KV 缓存占用率高的 worker 可能不得不逐出有用的缓存块、拒绝新的长提示词,或失去批处理效率。将更多请求路由到该 worker 会让延迟和吞吐量变得更糟。

感知 KV 缓存利用率的路由器可以将新请求引导到有足够内存余量的 worker。

感知 KV 缓存利用率的负载均衡器按负载将请求路由到 worker

关键点在于,一个好的路由器不会仅仅因为某个 worker 有正确的前缀就选择它。在饱和 worker 上命中缓存,可能仍然比在有余量的 worker 上较小的缓存命中更慢。

开源社区已经在着手解决方案。Gateway API Inference Extension 项目使用端点选择器(EPP)收集每个 worker 的 KV 缓存利用率、队列长度和 LoRA 适配器信息,并将请求路由到最优副本。

感知前缀的路由

感知前缀的路由(prefix-aware routing)尝试将请求发送到已经缓存了匹配前缀的 worker。

这之所以重要,是因为前缀缓存只有在请求到达能够复用缓存状态的 worker 时才起作用。在单个模型服务器中,缓存是本地的,很容易找到。在分布式部署中,每个 worker 都有自己的缓存,因此路由器需要某种方式来跨请求保持缓存局部性。

感知前缀缓存的路由器将请求发送到已缓存该前缀的 worker

不同的系统使用不同的方法来估计或跟踪缓存局部性:

  • 前缀亲和(prefix affinity)。使用亲和机制将具有相似前缀的请求路由到同一个 worker。一个简单的实现可以依赖客户端级别的会话亲和,例如将来自同一客户端 IP 的请求路由到同一后端。这易于实现,但当某个客户端或前缀变热时,可能会让 worker 过载。它还会错过跨不同客户端的缓存复用机会。
  • 感知前缀的一致性哈希。路由器对请求前缀的一部分进行哈希,使相似的提示词落在同一个或相邻的 worker 上。例如,一个简单的策略可以只哈希提示词的前 N 个 token 或字符。这个解决方案需要的路由元数据很少,但效果在很大程度上取决于哈希策略的质量和提示词长度分布。
  • 路由器上的近似前缀缓存。让路由器维护一个关于所有后端服务器上前缀缓存的近似查找缓存。这避免了详细的缓存上报,但路由器的视图在逐出或重启后可能变得陈旧。
  • 精确的感知缓存路由。worker 发出 KV 缓存事件或详细的缓存元数据,使路由器能够维护全局准确的缓存放置视图。例如,llm-d 使用来自 vLLM 和 SGLang 的 KV 缓存事件来跟踪缓存块在各服务 Pod 中的位置。然后调度器根据请求前缀在某个 worker 上已有多少可用来给 worker 打分。它还将缓存局部性与负载感知信号相结合,以避免让热门副本过载。

一般来说,更准确的缓存信号会改善缓存复用并减少重复的预填充工作。代价是更高的协调成本、更多的元数据交换,以及路由层中更多的工作。

感知预填充/解码的路由

当预填充和解码被拆分到不同 worker 上时,路由变得更加有趣。在分离式配置中,路由器可能需要决定:

  • 请求应该使用本地预填充还是独立的预填充 worker
  • 哪个解码 worker 应该拥有活跃的生成
  • KV 缓存应该如何在预填充和解码 worker 之间移动
  • 缓存局部性是否值得传输成本

对于短提示词,本地预填充可能更快,因为它避免了在 worker 之间移动 KV 缓存。对于缓存重叠高的长提示词,路由到能够复用现有缓存的 worker 可能更重要。路由策略必须同时考虑计算成本和数据移动。

这就是为什么推理路由不只是网络层的问题。它需要来自模型运行时、调度器和缓存管理器的信息。

混合打分

一些推理路由器会成为多信号打分系统。它们结合多种因素来估计每个 worker 上的服务成本。

  • 前缀和 KV 缓存命中概率。该 worker 是否已经包含请求前缀某部分的 KV 缓存?
  • KV 缓存内存利用率。有多少 GPU 内存已经被活跃的或缓存的 KV 状态占用?该 worker 是否接近逐出压力?
  • 队列长度。有多少请求在等待?
  • 活跃 token 和解码负载。当前正在生成多少个 token?这通常是解码期内存带宽压力的良好代理指标。
  • LoRA 适配器可用性。该 worker 是否已经加载了正确的适配器?中途交换适配器成本很高。
  • 预填充与解码角色。在分离式配置中,这个 worker 是专用于预填充还是解码?
  • SLA 或优先级类别。该请求是否有严格的延迟要求或更高的调度优先级?

一些开源项目已经朝这个方向推进:

  • SGLang 路由器 使用感知缓存的路由启发式,并带有负载均衡兜底。它使用基数树和队列数量跟踪近似的前缀局部性,然后根据系统的不均衡程度在感知缓存的路由和最短队列路由之间切换。
  • Dynamo 通过估计各 worker 上的预填充和解码成本来路由请求。它考虑 KV 缓存重叠、活跃解码块和工作负载放置,以减少冗余计算并提高服务效率。
  • llm-dGateway API Inference Extension 项目之上构建推理调度。它结合缓存局部性、worker 负载和其他运行时信号,在 Kubernetes 上提供智能的请求路由和调度。

常见问题

推理路由和负载均衡是一回事吗?

推理路由包含负载均衡,但范围更广。普通的负载均衡器主要是在后端之间分散流量。推理路由器还会考虑缓存局部性、KV 缓存内存压力、适配器状态、预填充成本、解码负载,以及请求优先级和 SLA。

感知缓存的路由总能降低延迟吗?

视情况而定。在过载 worker 上命中缓存,可能比在负载较轻的 worker 上较小的缓存命中更慢。好的路由器会将缓存重叠与队列和容量信号结合起来,而不是只优化缓存命中。

每个自托管的 LLM 部署都应该使用感知缓存的路由吗?

不一定。如果流量低、提示词短或缓存复用很少,可以从简单路由开始。当重复上下文和分布式 worker 使重新计算变得昂贵时,再添加感知缓存的路由。

其他资源