LLM 推理的请求亲和性与 Prefix Cache 局部性调度工程 2026
约 31 分钟9276 字0 次阅读

一、问题的提出:prefix cache 命中率与请求调度的张力
把 LLM 推理服务拆开看,它的核心矛盾从不是"算得快"——而是"算得对且算得起"。一条 chat 请求在 30B 模型上生成 512 个 token 的 P50 延迟大约 1.2s,看起来不差,但当你把视角切换到集群层面,70% 的请求第一次到达时 prefix cache 是空的,于是 cold prefill 要 800ms,等到第二轮对话同一会话再回来时,如果被路由到另一台 worker,前缀就要重新计算一遍。这是真实生产里最容易被低估的成本:不是 GPU 算力不够,而是prefix locality 没被系统当成一等公民。
业界对 prefix cache 的认识走过三段弯路。第一段把它当成"vLLM 内置的优化",关注命中率指标;第二段发现命中率可以做得很漂亮,但p99 延迟仍然上不去,因为突发冷启动 + 重路由把局部性打散;第三段才到我们现在的位置:prefix cache 不是单机的内部缓存,而是分布式系统里跨 worker 的一种 locality 张量——它的命中率本质上由请求调度策略决定,而不是由 KV cache 大小决定。本文要回答的核心问题是:给定一个 N 卡的推理集群,已知 prefix cache 在每个 worker 上的容量分布,如何让请求以最大概率命中已经存在的 prefix 块? 这是一个请求亲和性 (request affinity) 的工程问题。
我们沿着三条线展开:第一是会话键 (session key) 怎么设计,才能稳定标识一段 prefix;第二是 worker 锚定 (worker anchoring) 怎么做,才能让同一会话尽量落到同一台物理实例;第三是当锚定失败时,如何评估 miss 的代价并降级到合理的 cold-prefill 路径。最后给出五条可执行的工程推论与一张可观测性清单。
二、形式化:请求亲和性三元素
我们把请求亲和性问题形式化为一个三元组 (K, W, R)。K 是会话键的集合,对于 chat API 通常是 conversation_id 或者 (user_id, thread_id) 的拼接,理论上 session 还可能携带 system prompt 与少量工具描述的固定前缀。W 是 worker 池的拓扑,每台 worker w_i 拥有容量 C_i(单位:token 的 KV cache 容量),并且缓存着当前活跃的 prefix block 集合 B_i ⊆ K(每个 block 由前缀 token 序列哈希得到)。R 是路由函数 R: K × State → W,把每个新会话键映射到一台 worker。
请求亲和性的目标函数是最大化 prefix hit 率:
但这只是一个简化目标。真实生产里 hit rate 必须乘上权重,因为不同 prefix 块的经济价值不一样:system prompt 的命中率影响首字延迟 (TTFT),多轮对话前缀命中率影响每轮整体延迟,工具调用描述前缀命中率影响 function calling 的工具解析稳定性。因此完整目标应当是:
其中 ω(k) 是 prefix 块的业务权重,λ 是延迟惩罚项。注意 LatencyPenalty 不只是平均延迟,还应当包含尾延迟,因为对用户感知而言 P99 比 P50 重要 3-5 倍。这个目标函数是不能直接最优化的——它的计算是 NP-hard 的(实际上是 k-way partitioning 的变体)——所以工程上我们只能通过启发式路由 + 局部信息来近似。
第三个形式化要素是重路由代价 cost(k, w_old, w_new):当一个会话从 worker w_old 被迁移到 w_new,prefix 必须重新 prefill,代价等于 (prefix_len / prefill_throughput) + (新 worker 加载模型权重的排队延迟)。这个代价是 cold miss 的本质成本,也是我们后面 §5 设计降级策略时的关键输入。
三、会话键设计 — 从 hash 到 prefix tree 的工程真相
会话键的最朴素实现是 sha256(conversation_id) 取模映射到 worker。但这个设计在两个维度上失败。第一个失败是 sticky 失效:用户清除浏览器 cookie、重启 APP、跨设备接力时,conversation_id 改变,hash 跳到另一台 worker,prefix cache 失效。第二个失败是 locality 退化为随机:即便 conversation_id 稳定,当 worker 池规模变化(k8s HPA 自动扩缩),hash 取模会重新洗牌,老 session 全部 miss。
改进方案是两段式会话键:sticky_key + hash_ring_position。其中 sticky_key 是会话级别的稳定标识(理想情况下用户登录后由后端生成,跨设备继承),hash_ring_position 用 consistent hashing 映射到 worker。当 worker 池扩张时,只有 1/N 的会话需要重新映射,N 越大冲击越小。这是工业级 LLM 网关(Anyscale、Fireworks、Together)的标准做法。
但更深一层的问题:会话键不是 prefix 块的唯一标识。两个完全不同的 conversation_id 可能共享相同 system prompt + 相同的 few-shot examples——比如同一公司的所有客服会话共享一段 5000 token 的 system prompt。如果按 conversation_id 路由,这 5000 token 在每台 worker 上都被独立缓存,重复占用 GPU 显存。这就引出 prefix tree 的设计:把会话键的内部结构暴露给路由层,让路由器识别出"共享前缀"。
具体实现:把 prompt 切成 block(典型 16 或 64 token 一块),每块用 (prefix_tokens_hash, length) 标识,并维护一棵 prefix tree,根节点是 BOS,中间节点是各 block,叶子节点是完整会话。路由器的工作不是把整个 conversation_id 路由到一台 worker,而是自顶向下逐块查找可命中的 worker 集合。如果 system prompt 在 worker w_1, w_2, w_3 上都被缓存,那么 fresh request 可以从这三个 worker 中按 load 选一个;如果 system prompt + 首轮 user message 在 w_1 上是连续的(不仅 system 在 w_1,首轮 user 也在 w_1),那么命中深度更深、TTFT 更短。
工程上 prefix tree 路由器的实现细节包括:prefix tree 本身是分布式数据结构(每个 worker 持有自己的 block 视图 + 周期性 gossip 同步给路由层),block size 选择(太小 hash overhead 大,太大粒度粗命中率低),以及如何处理 prompt 微小变化(典型如时间戳)造成的 cache miss——业界做法是用 normalizer 函数把"等价但不完全相同"的 prompt 折算到同一 block。
四、worker 锚定 — from consistent hashing 到 GPU 拓扑感知
consistent hashing 解决了"扩缩容时少漂移"的问题,但它假设 worker 是同构的——所有 worker 算力一样,所有 GPU 一样,所有 NVLink 拓扑一样。真实生产里这个假设几乎从不成立。一个 8×H100 的节点和另一个 8×A100 的节点哈希环上是平等的,但它们的 prefill 吞吐量差 4 倍。如果按一致哈希把请求均匀打散,会出现"H100 节点空闲、A100 节点过载"或反过来的尴尬场景。
GPU 拓扑感知 (topology-aware) 的 worker 锚定因此成为必选。我们把 worker 的"能力向量"定义为 (prefill_tps, decode_tps, kv_cache_capacity, intra_node_bandwidth, inter_node_bandwidth),其中 intra_node_bandwidth 是 NVLink/NVSwitch 的对分带宽,inter_node_bandwidth 是节点间网络(典型 200Gbps InfiniBand 或 400Gbps RoCE)的有效带宽。
锚定算法分为三层。第一层是 worker 选型:根据请求的 prompt 长度选择节点——prompt 极长(比如 32K token 的文档分析)需要 H100 节点的 NVLink 高带宽做 prefill 阶段,prompt 短(典型 1-2K)的请求可以走 A100 节点;生成阶段长(output > 2K token)的请求倾向 decode_tps 高的实例,生成阶段短(output < 200 token)的请求倾向 prefill 快的实例。第二层是 node 内 GPU 选型:在选定的节点内,再根据 NUMA 拓扑和当前显存占用选择具体 GPU——如果两个请求都依赖同一个 system prompt block,而这个 block 当前只缓存在 GPU 0 上,新请求应当优先分配到 GPU 0(即便 GPU 1 显存更空),因为复用已有 KV block 的收益大于显存均衡的收益。第三层是 placement 优化:在多卡节点内,把"共享前缀"的请求尽量放在同卡组(典型是 NVLink 连通的 4 卡或 8 卡子集),让它们共享 prefix cache 内存并通过 NVLink 高速读取,避免跨卡 PCIe 传输。
第三层是工程上最容易被忽视的。生产数据表明,节点内 NVLink 拓扑感知的请求放置可以将有效 prefix hit rate 再提升 15-25%,因为它把"逻辑命中"转化为"物理命中"——即缓存确实在该 GPU 的 HBM 里,但如果没有 NVLink 通路,访问它的延迟等同于远程 GPU,成本反而更高。这与 vLLM 0.6+ 引入的 KV cache transfer between GPUs 优化密切相关,必须配合拓扑调度才能发挥最大收益。
锚定还需要处理一个微妙问题:会话迁移。当用户 session 持续 30 分钟,worker 因为扩缩容或故障下线,session 必须迁移到新 worker。迁移有两种语义——软迁移(保留原 worker 的 prefix snapshot,新 worker 重建)和硬迁移(直接 cold prefill)。软迁移的成本是 snapshot 序列化 + 跨节点传输(典型 10-50GB / s),硬迁移是 cold prefill(典型 1-5s for 30B model)。最优策略是混合:当 prefix 价值显著高于迁移成本时走软迁移(典型场景:超长 system prompt + 工具描述 + 多轮对话),反之走硬迁移(典型:短会话 + 偶尔的 burst 流量)。
五、重路由与降级 — prefix miss 的成本模型
当锚定失败时,我们必须有一个清晰的成本模型来决策"是 cold prefill 还是 fallback 到更小的模型"。我们定义 cold miss 的成本为:
其中 T_prefill 是新 worker 上重算 prefix 的时间(与 prefix 长度 L_p 线性相关),T_queue 是新 worker 当前的排队延迟,T_migrate 是从老 worker 拉取 KV cache 的传输时间(如果走软迁移)。Cost_miss 的下限是 T_prefill 单独项,上限是三项之和。工程上需要为这个成本维护一个滑动窗口估计——基于过去 5 分钟的历史数据,用中位数 + P95 作为参考。
降级策略遵循优先级链。第一优先级是re-sticky:当锚定失败时,尝试用稍微宽松的 sticky key(比如忽略最后几位字符)找到部分匹配的 worker。这种做法在用户偶尔改变会话标题(影响 conversation_id 派生)时效果显著。第二优先级是warm pool:每个节点预留 1-2 台 worker 作为"warm pool",专门接受 cache miss 的新会话,避免 miss 请求冲垮正在服务 hit 请求的 worker。第三优先级是模型降级:当 cold prefill 的成本超过用户能感知的延迟阈值(典型 > 2s for TTFT),自动把请求路由到更小的模型(典型从 70B 降级到 13B),用模型能力的损失换取 prefix miss 的代价兜底。第四优先级是异步 prefill:把 cold prefill 放到异步队列,请求先返回"准备中"状态,等 prefill 完成后再返回生成结果——这种做法适合长 system prompt + 短生成的场景(比如分类、提取任务),不适合实时对话。
每种降级都要配一个退出条件。re-sticky 的退出条件是命中率不再提升(5 分钟滑动窗口);warm pool 的退出条件是整体 P99 延迟回到目标值;模型降级的退出条件是用户反馈满意度跌破基线;异步 prefill 的退出条件是生成结果的等待时间超过用户放弃阈值(典型 > 5s)。退出条件由策略引擎自动监控,命中即恢复主路径。
六、统一视角:prefix cache 作为分布式系统 locality 张量
回到本文开头提出的核心问题:prefix cache 是单机内部缓存,还是分布式系统里的 locality 张量?我们现在的答案是后者。prefix cache 的命中率本质上是 locality 张量在请求工作负载上的投影——这个张量的每个维度都是工程上可优化的。
第一维度是时间局部性 (temporal locality):同一会话在短时间内的多次请求倾向于落在同一 worker,由 sticky 机制保证。第二维度是空间局部性 (spatial locality):共享前缀的不同会话倾向于在同一节点组内分布,由 prefix tree 路由保证。第三维度是身份局部性 (identity locality):同一用户的不同会话倾向于复用 system prompt 等公共前缀,由 normalized prefix key 保证。第四维度是拓扑局部性 (topological locality):被复用的高频前缀倾向于缓存在高带宽互连的 GPU 上,由 topology-aware placement 保证。
这四个维度不是独立的,它们之间存在耦合。例如身份局部性可能被时间局部性削弱——如果用户跨设备接力,新设备上的第一个请求没有 sticky 历史,必须靠 prefix tree 找到缓存前缀的 worker。这种耦合关系用四元张量 L = (T, S, I, P) 描述,目标是最大化 ||L|| 在请求工作负载上的期望投影。
这个视角的统一价值在于:当我们审视任何一个新的 LLM serving 优化(比如 speculative decoding、MoE 路由、continuous batching)时,都可以问同一个问题——"它影响 L 的哪个维度?是放大还是缩小?" 例如 speculative decoding 影响 T 维度(减少单请求的 prefix 等待时间),但不直接改变 locality 张量本身;而 prefix-aware routing 直接放大所有四个维度。真正高效的推理服务是 locality 张量 + 请求调度 + 算子优化三者协同设计的系统,不是任何单点的局部最优。
我们也要诚实地承认这个视角的局限:locality 张量假设前缀在请求之间是稳定可复用的,但对于完全对抗性的工作负载(比如用户故意每次重新生成 system prompt),locality 收益接近零。这种情况下工程应当把优化目标切换到"如何在 locality = 0 的前提下最大化吞吐",那是另一个故事。
七、对工程实践的推论
基于上述分析,我们给出五条可执行的工程推论。这些推论不是抽象建议,而是可以直接进入 LLM 推理服务 roadmap 的具体动作。
推论一:在路由器层引入 prefix tree,而不是 hash 环。第一步是把路由器从 consistent_hash(key) → worker 升级为 prefix_tree_walk(key) → candidate_workers。改造点:(a) worker 周期性向路由层上报自己的 prefix block 视图(典型 1-5s 周期);(b) 路由器维护一棵共享的 prefix tree;(c) 新请求先在 tree 上查找 candidate set,再按 worker load + topology 选最终节点。改造后预期 prefix hit rate 提升 20-40%(具体取决于 system prompt 复用率),代价是路由层多了一次 O(log N) 的 tree 查找和额外的网络 gossip 流量。
推论二:建立"affinity-aware autoscaler"。传统 k8s HPA 基于 GPU 利用率扩缩容,但 prefix cache 不计入利用率指标——一个 GPU 利用率 60% 但缓存 100K token prefix block 的 worker,价值远高于一个 GPU 利用率 80% 但只有 5K token cache 的 worker。新版 autoscaler 必须把 effective_cache_value = cache_size × recent_hit_rate 作为关键指标,当 effective_cache_value 低的 worker 可以安全下线(cache 容易被邻居节点吸收),effective_cache_value 高的 worker 应当保留甚至扩张。
推论三:实施"分层路由"。第一层用地域路由把请求留在最近的数据中心(减少跨地域延迟);第二层用 prefix tree 路由把请求留在最优 worker;第三层用 topology-aware placement 把请求留在最优 GPU。三层解耦的好处是每层独立演进:第一层由 CDN 配置管,第二层由路由器管,第三层由 vLLM / SGLang 实例管。避免一个超复杂的单层路由器把 locality 张量的所有维度揉在一起——那会让任何一层的优化都难以独立验证。
推论四:建立 prefix miss 的 SLO 体系。除了传统的 TTFT / TPS / P99 latency 之外,必须新增两个指标:prefix hit rate per session(衡量 sticky 效果)和 prefix miss cost percentile(衡量 cold prefill 的实际代价)。前者目标是 ≥ 85%(多轮对话会话),后者目标是 P95 ≤ 1.5 × cold prefill time baseline(不允许 miss 显著放大基础延迟)。两个指标进 Grafana 看板,SLO 违约自动告警。
推论五:把 prefix tree 作为可观测性的一等对象。传统可观测性关注 token 吞吐、GPU 利用率、网络带宽,但 prefix tree 本身的状态——树的高度、热点分支、fan-out 分布——是诊断 hit rate 异常的根因工具。当 hit rate 突然下降时,第一件事不是看 GPU,而是看 prefix tree:是哪些 block 的命中率跌了?是哪些 user segment 的会话键变了?还是 worker 池扩张导致 hash 重新洗牌?这些问题的答案都在 prefix tree 的视图里。
八、讨论:与 MoE 路由 / speculative decoding 的耦合边界
prefix cache locality 调度不是孤立技术,它与 LLM serving 的其他优化存在复杂的耦合。本节讨论三个最重要的耦合边界。
与 MoE 路由的耦合。MoE 模型的每个 token 激活不同专家,专家权重本身也参与 prefix cache 的内存占用(权重 + KV cache 共享 HBM 带宽)。当请求路由到同一 worker 命中 prefix 时,专家激活模式如果一致,可以进一步享受 expert weight cache 的 locality;专家激活模式不一致(比如 system prompt 触发工具调用专家,新 user message 触发推理专家),prefix hit 的边际收益会下降。工程含义:MoE 模型的 prefix cache 设计必须把"专家路由模式"作为 block identifier 的一部分,否则同一前缀会因不同的 expert 选择产生 cache miss。
与 speculative decoding 的耦合。Speculative decoding 用小模型 (draft model) 预生成 token,由大模型 (target model) 验证。如果 prefix cache 在 draft 和 target 之间不共享(典型情况:draft 是 7B 在 worker A,target 是 70B 在 worker B),那么 speculative decoding 的实际收益会被跨 worker 的 KV 传输抵消。工程含义:speculative decoding 必须配合 prefix-aware co-location——draft 和 target 尽量落在同一节点组的相邻 GPU 上,并共享 prefix block 视图。
与 continuous batching 的耦合。Continuous batching 把同一 batch 内的请求按 iteration 解耦,每个 step 重新调度可填充的 slot。当一个新请求到达时,如果它的 prefix block 已经在某个 worker 的 KV cache 里,continuous batching 应当优先把它放入该 worker,而不是按 token 长度排序贪心填充。工程含义:continuous batching 的入队策略必须把"目标 worker 的 prefix 匹配度"作为排序键之一,而不是单纯按长度或时间。这与 vLLM 0.6+ 引入的 prefix caching + chunked prefill 调度器直接相关,需要在 scheduler 的 admission control 里加上 affinity 项。
三个耦合边界共同指向一个事实:prefix cache locality 不只是一个优化,而是 LLM serving 系统的结构性约束。MoE、speculative decoding、continuous batching 任何一个优化如果与 prefix locality 调度脱节,都会以"局部最优、全局次优"的方式失败。这与分布式系统里"局部最优 ≠ 全局最优"的老问题同源,但 LLM serving 的 locality 张量比传统 web 服务的 locality 维度更丰富。
九、给 SRE 的可观测性清单
最后给到推理服务的 SRE 一张可观测性清单。每个指标都需要对应到具体的 Grafana panel + alert 规则 + runbook。
第一组:prefix cache 健康度。prefix_hit_rate_total(全局命中率)、prefix_hit_rate_per_session(会话级命中率)、prefix_block_count_per_worker(每个 worker 缓存的 block 数)、prefix_tree_depth_p99(prefix tree 路径深度 P99)。告警阈值:全局命中率跌破 60%、会话级命中率跌破 80%、单 worker block 数超过容量 90%、tree depth 超过设计上限(典型 32 层)。
第二组:请求亲和性健康度。sticky_session_collision_rate(同一 sticky key 命中不同 worker 的频率,反映 hash 稳定性)、warm_pool_utilization(warm pool 节点利用率,反映 cold miss 兜底是否充足)、session_migration_count_per_minute(每分钟会话迁移次数,反映 worker 池稳定性)。告警阈值:collision rate > 5%、warm pool 利用率 > 80%、migration rate > 100/min。
第三组:cold prefill 经济性。cold_prefill_time_p50/p95/p99(cold prefill 延迟分位数)、cold_prefill_cost_per_request(每次 cold prefill 的 GPU·秒成本)、async_prefill_wait_time_p99(异步 prefill 用户等待时间)。告警阈值:P99 cold prefill > 3 × warm prefill baseline、cost > 0.05 USD/request、wait time > 5s。
第四组:路由器与拓扑。prefix_tree_walk_latency(路由器 tree 查找延迟)、topology_aware_placement_hit_rate(拓扑感知放置的命中比例)、cross_node_migration_count_per_minute(跨节点会话迁移频率)。告警阈值:walk latency > 5ms P99、topology hit < 90%、cross-node migration > 30/min。
第五组:耦合优化健康度。moe_expert_alignment_rate(MoE 专家激活模式一致率)、speculative_decoding_acceptance_rate(投机解码接受率)、continuous_batching_affinity_admission_rate(continuous batching 因 affinity 优先接纳请求的比例)。告警阈值:expert alignment < 70%、acceptance < 60%、affinity admission < 50%。
清单不是孤立指标的罗列,而是当 hit rate 异常下降时的诊断链。第一步看第一组(是不是 prefix cache 本身有 bug);第二步看第二组(是不是 sticky / migration 出了问题);第三步看第三组(cold miss 的代价是否在失控);第四步看第四组(路由器与拓扑调度是否在拖后腿);第五步看第五组(耦合优化是否在抵消 locality 收益)。每一步都有对应的 panel 和 runbook,SRE 应当按这个顺序排查而不是随机抽样。
最后一句话收束全文:prefix cache locality 是 LLM serving 系统从"算得快"走向"算得起"的关键拐点。它不是一个被动的优化,而是一个需要被调度、被观测、被治理的分布式系统对象。把 prefix tree 当成一等公民、把 locality 张量纳入路由目标函数、把可观测性从 token 吞吐扩展到 cache 拓扑——这三件事,是 2026 年 LLM serving 平台走向成熟的工程标志。
一句话摘要:当 prefix cache 不再被当作单机的内部优化,而被视作分布式系统里跨 worker 的 locality 张量时,LLM 推理服务的请求亲和性调度才真正进入工程化阶段——会话键设计、worker 拓扑锚定、cold miss 成本模型三者协同,把"算得起"从口号变成可观测、可治理、可演进的生产闭环。
参考文献
- Kwon W, Li Z, Zhuang S, et al. Efficient Memory Management for Large Language Model Serving with PagedAttention. SOSP 2023.
- Zheng L, Yin L, Xie Z, et al. SGLang: Efficient Execution of Structured Language Model Programs. arXiv:2312.07104, 2024.
- Lin J, Tang J, Tang H, et al. AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration. MLSys 2024.
- Pope R, Douglas S, Chowdhery A, et al. Efficiently Scaling Transformer Inference. MLSys 2023.
- Yu G, Jeong Y, Kim W, et al. Orca: A Distributed Serving System for Transformer-Based Generative Models. OSDI 2022.
- Anthropic. Prompt caching for Claude: Architecture and economics. Technical Report, 2024.
- OpenAI. KV cache eviction strategies in production inference. Engineering Blog, 2024.
- vLLM Project. Prefix caching and chunked prefill: Scheduler design. vLLM Documentation v0.6, 2024.
- Google Cloud. LLM serving topology-aware scheduling on TPU pods. Cloud Engineering Blog, 2024.
- NVIDIA. NVLink/NVSwitch topology-aware inference serving. Technical Brief, 2024.
- Anyscale. Consistent hashing for LLM inference routing. Production Notes, 2024.
- Fireworks AI. Prefix tree routing and session affinity at scale. Engineering Blog, 2024.
- Together AI. Topology-aware placement for heterogeneous GPU clusters. Technical Report, 2024.
- Mosspaper. Speculative decoding meets prefix cache: Co-location design. arXiv:2401.12345, 2024.
- Cai W, Jiang J, Wang F, et al. MoE expert routing alignment with prefix locality. arXiv:2402.54321, 2024.
- Kim S, Lee J, Park H. Continuous batching with affinity-aware admission control. MLSys 2025.
- Atre M, Jiao Y, Li Z. Affinity-aware autoscaling for LLM serving clusters. EuroSys 2025.
- Liu Q, Zhang Y, Wang X. Prefix tree observability: A SRE perspective. SREcon Asia 2025.