跳到主要内容

多区域 LLM 服务:没人警告过你的缓存局部性问题

· 阅读需 12 分钟
Tian Pan
Software Engineer

当你在多个区域运行无状态 HTTP API 时,路由问题基本上已经解决了。在前面放一个全球负载均衡器,按地理位置分配请求,最糟糕的情况也不过是缓存项稍微过时。任何副本都可以处理任何请求,并获得相同的结果。

LLM 推理打破了每一个假设。一旦你添加了提示词缓存(Prompt Caching)——你肯定会加,因为缓存命中和未命中的成本差异大约是 10 倍——你的服务就会以大多数基础设施团队预料不到的方式变得有状态,直到他们在第二个区域看到延迟数据退化。

根本原因在于 KV 缓存局部性(KV cache locality)。当语言模型处理提示词时,它会为该提示词中的每个 token 计算键值张量(key-value tensors)并将其存储在 GPU 显存中。下一个与缓存提示词共享前缀的请求可以跳过重新计算这些张量——但前提是该请求落在了持有该缓存的同一个 GPU 节点上。跨区域路由不仅仅是导致缓存未命中,它会让缓存变得像从未存在过一样。

为什么多区域 LLM 服务不同于多区域 API

考虑一下当你给一个调优良好的单区域 LLM 部署添加第二个区域时会发生什么。在区域 A,你通过重复的系统提示词、共享的对话前缀以及附加在大多数请求前的 RAG 上下文分块,建立起了温缓存(warm caches)。你的缓存命中率维持在 60–70%。区域 A 的用户看到的延迟始终保持在低水平。

区域 B 启动时是冷启动。每个请求都是全上下文重新计算。延迟飙升。你增加了更多的 GPU。成本上升。你告诉自己过几天就会变暖,确实如此——但现在你遇到了新问题。你的全球负载均衡器在执行感性操作时,偶尔会因为区域 A 负载过重而将区域 A 用户的请求路由到区域 B。该请求命中了冷缓存,你会在 P95 指标中看到延迟飙升。你根据用户 ID 添加了粘性会话(sticky sessions)。延迟恢复正常。一周后,你意识到按用户 ID 建立粘性会话意味着你的负载分布不均,因为某些用户产生的流量是其他人的 10 倍。

这就是模式。每一次修复都会揭示下一个问题,而且它们都不是代码中的 bug——它们是无状态基础设施假设与有状态推理需求之间的架构不匹配。

KV 缓存是你真正的状态单元

在传统的分布式系统中,状态存在于数据库中。你在无状态计算和有状态存储之间有明确的分离。扩缩容是可预测的。

在 LLM 服务中,KV 缓存驻留在运行预填充(prefill)阶段的 GPU 上。它与产生它的计算任务位于同一位置,在内存压力下会被逐出且没有持久记录,并且没有用于外部检查的 API。发送到两个不同节点的两个相同请求会导致两次缓存未命中,尽管从系统设计的角度来看,“正确”的行为应该是共享该计算结果。

这与在其他地方都行之有效的扩缩容策略产生了根本性的冲突。轮询(Round-robin)负载均衡对无状态服务是正确的,但对带缓存的 LLM 推理却是有害的。每一个没有落在具有匹配前缀节点上的请求都要支付完整的预填充成本。在大规模场景下,长系统提示词的预填充可能需要数百毫秒,这不仅仅是一个小小的效率问题——它是 P50 延迟 80ms 与 800ms 之间的区别。

实际后果是:你的负载均衡器需要了解 KV 缓存状态,而不仅仅是节点健康状况和队列深度。通用的 HTTP 负载均衡器没有这些信息。这就是为什么会出现像 vLLM Router 这种专用路由器的原因——它用 Rust 编写以最小化开销,并专门设计用于消费来自推理引擎的 KV 缓存事件。它们基于前缀哈希匹配进行路由,而不仅仅是轮询或最小连接数。

基于前缀的一致性哈希是单区域部署的正确默认选择。你对提示词的前 N 个 token 进行哈希处理,将该哈希映射到一个节点,并进行相应路由。添加有界负载约束,使单个节点不会过载,这样你就拥有了一个合理的稳定状态。经过研究支持的实现方案是有界负载一致性哈希(CHWBL),它是专门为这个问题设计的。

数据驻留要求打破了你的缓存优化

如果你为欧盟用户提供服务,你最终会遇到数据驻留(Data Residency)要求。最简单的版本是:用户数据不得在欧盟以外处理。更严格的版本——随着组织对云服务商管辖权变得更加谨慎而变得越来越普遍——是数据甚至不能经过总部位于美国的提供商基础设施,因为可能存在《云法案》(CLOUD Act)的风险。

此时,架构上的张力变成了真正的冲突。当缓存预热的请求留在同一个节点或同一个区域集群时,提示词缓存最为有效。但数据驻留要求欧盟用户的数据永远不能离开欧盟区域。这两个约束单独来看都是可以满足的。但结合在一起,这意味着你不能根据缓存可用性来路由欧盟流量——你只能在欧盟部署内路由它。

结果是:你的欧盟区域拥有的缓存池比你的全球区域小,仅此而已。如果你的欧盟用户群较小,缓存预热速度就会较慢。如果欧盟用户具有多样化的查询模式和冗长的、独特的系统提示词,命中率可能会永久保持在低水平。你在欧盟支付的单 token 成本将高于你的主要区域,这不是因为基础设施定价差异,而是因为你的缓存架构受到了合规性的限制。

摆脱这一困境的唯一方法是对欧盟数据停止依赖云托管的 LLM 推理。可以在单个本地服务器上运行的小型模型完全避免了多区域问题——如果只有一个区域,就不存在跨区域路由。这是一个现实的权衡,那些做出“进军欧盟”决策的团队往往只有在围绕云 API 构建了缓存策略之后才会发现这一点。

模型权重同步与版本偏差

Prompt 缓存和路由是多区域 LLM 服务中的实时问题。而模型权重同步则是一个“慢性”问题,往往会在发布过程中让你措手不及。

当你微调模型或更新到新的基础版本时,需要在切换流量之前将这些权重分发到每个区域。对于一个 bf16 格式的 70B 参数模型,每个区域大约有 140GB 的数据。带有复制时间控制(Replication Time Control)的 S3 跨区域复制可以保证 99.99% 的对象在 15 分钟内完成复制,但“15 分钟内”意味着你会面临一个时间窗口:区域 A 运行的是新模型,而区域 B 运行的仍是旧模型。

对于大多数应用来说,这个窗口期是可以接受的。但在某些情况下,这至关重要:

  • Prompt 格式变化:如果新模型需要不同的系统 Prompt 格式,你需要协调 Prompt 更新与模型更新,否则在复制窗口期内,某个区域可能会出现 Prompt 和权重不匹配的情况。
  • 评估数据集:针对生产端点运行的自动评估(Evals)可能会在发布期间看到不一致的结果,因为它们命中了不同区域中不同的模型版本。
  • 缓存前缀失效:当你更新模型时,由旧模型计算的现有 KV 缓存条目将失效。不同的模型会对相同的 Prompt 生成不同的 KV 张量(Tensors)。随着新权重的传播,你的路由逻辑需要按区域清除或失效缓存。

能够避免大多数此类问题的操作实践是:将模型发布视为每个区域的蓝绿部署(Blue-Green Deployments),而不是原地权重替换。并行运行新旧版本,直到新权重完全复制并预热,然后切换流量。这在发布期间会使你的 GPU 需求翻倍,成本虽然很高,但它完全消除了版本偏差(Version-Skew)窗口。

真正具备扩展性的路由架构

基于上述情况,这里有一个能够处理多区域 LLM 服务且无需专门平台团队维护的路由架构:

第一层:基于全局 DNS 的路由。根据地理位置和合规性要求将请求引导至正确的区域集群。例如,来自欧盟的请求仅分发给欧盟集群。这一层不感知 LLM 的特定状态——它只负责地理位置。

第二层:区域网关。负责身份验证、速率限制和成本追踪。Cloudflare AI Gateway 或 LiteLLM 等托管解决方案可以部署在这里。在这一层,你还可以实现模型路由——将简单的查询发送给较小的模型,将复杂的查询发送给较大的模型——而无需干扰推理节点。

第三层:缓存感知的负载均衡器。使用前缀哈希一致性路由(Prefix-hash Consistent Routing)将请求分发到区域内的推理节点。这是 vLLM Router 或类似的专用组件所在的层级。它接收来自推理引擎的 KV 缓存事件,维护哪些前缀缓存在哪些节点上的视图,并在保持负载均衡的同时最大化缓存命中率。

第四层:带有本地 GPU KV 缓存的推理节点。从缓存的角度来看,这些节点实际上是有状态的,但上层的路由层向系统的其余部分屏蔽了这种有状态性。

该架构中一个具有前瞻性的补充是 KV 缓存解耦(KV Cache Disaggregation):将 KV 缓存从 GPU 转移到分布式对象存储(如 Redis Cluster 或 LMCache 等专门系统)中,以便区域内的任何推理节点都能访问任何缓存前缀。这打破了路由与特定 GPU 节点之间的强耦合(Affinity),从而实现了更灵活的扩展。这目前还不是默认的部署模式,但它是生产服务生态系统的发展方向。

你不该过度设计的地方

多区域 LLM 服务本身已经足够复杂,很容易导致过度架构。以下是一些听起来很有必要但通常并非如此的事情:

跨区域 KV 缓存共享:在区域之间移动 KV 张量的网络延迟几乎总是大于缓存命中带来的延迟节省。请保持缓存的区域化。

实时跨区域缓存状态同步:与上述点相关。你的全局负载均衡器不需要知道哪个区域缓存了哪些前缀。它只需要知道用户所属的合规区域以及地理位置带来的延迟。仅此而已。

具有全局会话亲和性的双活(Active-active)架构:你可以跨区域运行双活架构,而不需要要求任何单个用户的请求始终进入同一区域。区域内的粘性(通过一致性哈希)足以获得大部分缓存收益。全局亲和性增加了复杂性,除非你有特定的跨越长时间段的多轮对话工作负载,否则很少能获得回报。

适用于大多数团队、大多数规模的架构是:区域隔离配合区域内缓存感知路由,通过每个区域的蓝绿部署进行模型发布,并在 DNS/网关层强制执行合规边界。当你的缓存命中率进入瓶颈且 GPU 资源仍然受限时,再考虑引入解耦的 KV 存储。这就是决策树。

运维实操中的现实

多区域 LLM 推理服务并不比多区域 API 服务复杂得多,但它需要你理解哪些传统分布式系统的假设可以借鉴,而哪些不行。负载均衡器需要变得更加智能。部署流程需要考虑缓存失效问题。数据驻留要求对缓存策略的约束,会以影响单位经济效益的方式体现出来。

那些陷入困境的团队,往往是将 LLM 推理像部署无状态微服务那样进行部署,结果在增加第二个区域后,发现延迟并没有像预想的那样得到改善,并对此感到意外。而表现出色的团队会从一开始就将 KV 缓存局部性(KV cache locality)视为一等架构设计要素,并在启动第二个区域之前——而不是之后——围绕这一约束构建其路由层。

References:Let's stay in touch and Follow me for more thoughts and updates