27届大模型面试准备(七十):大模型推理服务的负载均衡与智能请求路由
引言
本篇是「工程实战深化」系列的第 70 篇(大模型线)。上一篇(六十九)讲了 Prefill-Decode 分离:把一个大模型推理集群拆成 prefill 池和 decode 池,各自扩缩。拆完之后立刻冒出一个问题——请求该去哪个实例?这看似是「负载均衡」的老话题,但大模型推理的负载均衡比普通 Web 服务难得多:请求长度天差地别、KV Cache 有明显的位置局部性、GPU 利用率和延迟不是线性关系。
本篇把推理服务的请求路由讲透:为什么朴素轮询会翻车、最短队列路由怎么做、前缀亲和路由怎么提升缓存命中、代价感知路由怎么防长请求饿死,最后给一套可背的面试速答。建议和(六十九)PD 分离、(六十六)推理调度、(六十三)前缀缓存串起来看。
1. 为什么推理服务的负载均衡特别难
普通微服务的负载均衡假设「每个请求代价差不多」,轮询/一致性哈希基本够用。大模型推理不满足这个假设:
普通 Web 服务 大模型推理服务 ┌─────────────┐ ┌───────────────────────────┐ │ 请求代价≈均等 │ │ 请求代价方差极大 │ │ 无状态 │ │ 有状态(KV Cache 局部性) │ │ 延迟线性可加 │ │ 延迟非线性(GPU 利用率拐点) │ └─────────────┘ └───────────────────────────┘ 推理请求长度分布示例: prompt 长度: 50 ──────────────────────────── 8000 token 输出长度: 10 ──────────────────────────── 4000 token (长思维链) → 单请求算力/显存占用差 100 倍以上三个难点:
- 代价方差大:一个 50 token 短问答和一个 8000 token RAG + 4000 token 长思维链,算力差百倍,轮询会把长请求堆积到个别实例。
- KV Cache 局部性:相同 system prompt 的请求落在同一实例能命中 prefix cache(六十三),路由破坏了局部性就白算。
- 延迟非线性:GPU 在某并发下利用率陡增、TBT 指数恶化(呼应六十一排队论),不能只看「平均负载」。
2. 常见路由策略对比
策略 路由依据 优点 缺陷 ────────────────────────────────────────────────────────────── Round Robin 依次轮流 简单、均匀 忽略队列长度与代价 一致性哈希 key 哈希 稳定、可粘性 负载倾斜、无视实时 Least-Queue 各实例 inflight 数 防堆积 忽略请求代价差异 Prefix-Affinity 前缀 hash 保 prefix cache 可能打破负载均衡 Cost-Aware 预估 token/显存 防长请求饿死 需准确代价估计 Hybrid 组合上述 综合最优 实现复杂下表是工程上最常用的两种「智能路由」:
| 策略 | 适用场景 | 核心指标 | 风险 |
|---|---|---|---|
| Least-Queue(最短队列) | decode 池、输出长度相近 | inflight 序列数 + 预估剩余 token | 长输出请求仍可能扎堆 |
| Prefix-Affinity(前缀亲和) | prefill 池、固定人设/RAG | prompt 前缀 hash | 热点前缀实例过载 |
| Cost-Aware(代价感知) | 混合长度、要严格 SLA | 预估总 token × 单价 + 显存 | 预估不准则失效 |
3. Least-Queue 最短队列路由
思路:每个实例上报「当前 inflight 序列数」和「预估剩余待生成 token 数」,路由选「预计最快空闲」的实例。
实例 A: inflight=8, 剩余预估=800 token 实例 B: inflight=3, 剩余预估=1200 token 实例 C: inflight=5, 剩余预估=300 token 新请求预估输出=200 token → 选 C (剩余最少, 最快腾出容量)注意不能只看 inflight 数量,要看「剩余工作量」:一个 inflight=3 但每个都要生成 2000 token 的实例,可能比 inflight=8 但每个只剩 50 token 的实例更忙。
# 伪代码:Least-Queue 路由(带剩余工作量估计)defleast_queue_route(req,instances):best,best_score=None,float("inf")est_out=estimate_output_len(req)# 预估本请求输出长度forinstininstances:load=inst.inflight_seqs*avg_decode_cost\+inst.remaining_tokens# 已排队剩余 tokenscore=load+est_out*decode_cost(inst)ifscore<best_score:best,best_score=inst,scorereturnbest4. Prefix-Affinity 前缀亲和路由
核心直觉:把共享同一段前缀(固定人设、few-shot、RAG 公共文档)的请求,路由到持有该前缀 KV Cache 的同一实例,命中 prefix cache(呼应六十三/六十九),省算力、稳 TTFT。
prompt = [system][few-shot][user_query] └── 公共前缀 ──┘ └─ 变化 ─┘ hash(公共前缀) → 固定实例 P3 → 所有用同一 system 的请求都去 P3,前缀段只算一次实现要点:
- 取 prompt 的「稳定前缀」做 hash(注意 user_query 会变,不能整段 hash)。
- 维护「前缀 → 实例」映射表;实例下线时把映射失效,请求回退到普通路由并重算前缀。
- 和 Least-Queue 冲突时,优先保前缀亲和(除非该实例过载触发熔断)。
5. Cost-Aware 代价感知路由
当 SLA 严格、请求长度差异大时,用「预估代价」做路由:代价 = (prompt_len × prefill_cost) + (output_len × decode_cost) + KV 显存占用。路由选「加入后负载仍最低」且「不超显存上限」的实例。
defcost_aware_route(req,instances):p,o=len(req.prompt),estimate_output_len(req)kv_mem=p*KV_PER_TOKEN# 本请求 KV 显存best,best_util=None,float("inf")forinstininstances:ifinst.free_kv_mem<kv_mem:# 显存硬约束continueutil_after=(inst.busy_compute+p*prefill+o*decode)\/inst.capacityifutil_after<best_util:best_util,best=util_after,instreturnbestorfallback_least_queue(req,instances)代价:需要较准的长度预估。工程上常用「历史同类请求长度分布」或「prompt 长度 + 简单回归」估算输出长度,误差大时退化为 Least-Queue。
6. 混合路由:工程落地最常用
真实网关几乎都是 Hybrid:先按前缀亲和保缓存,再在「命中前缀的实例集合」内做 Least-Queue/Cost-Aware 选最空的一个;若集合全过载则放宽到全局并触发前缀重算。
请求 → hash(前缀) → 候选实例集合 {P3,P7} └─ 集合内 Least-Queue → P7 (更空) 若 {P3,P7} 全过载 → 全局 Cost-Aware → P2 (牺牲前缀缓存, 保 SLA)7. 网关、限流与优先级
路由之上还要一层网关做全局治理(呼应六十六):
- 全局并发上限:实例总容量 = Σ 各实例 max_seqs;超过则排队或拒绝。
- 令牌桶限流:按用户/应用维度限 QPS,防单租户打爆(呼应六十六多租户)。
- 优先级队列:VIP 请求插队,但要对低优先做公平性保护,避免饿死。
- 熔断:某实例错误率/延迟超阈值,路由暂时剔除它(呼应七十下篇智能体侧同理)。
# 伪代码:带优先级与熔断的路由网关classRouteGateway:defroute(self,req):ifself.rate_limiter.deny(req.tenant):# 租户限流returnRESP_TOO_MANYinsts=self.candidates(req)# 前缀亲和集合insts=[iforiininstsifnotself.cb.open(i)]# 剔除熔断实例ifnotinsts:insts=[iforiinself.allifnotself.cb.open(i)]target=self.hybrid_select(req,insts)# Least-Queue/Cost-Awareself.cb.record_attempt(target)returntarget8. 生产落地 checklist
- 监控每实例 inflight、剩余 token、KV 显存占用、prefix cache 命中率、P99 TBT。
- 路由决策周期要短(秒级),否则负载快照失真。
- 前缀映射表要支持失效与迁移,避免实例扩缩时缓存全灭。
- 压测用真实长度分布,别用固定长度(会掩盖路由倾斜)。
- 熔断阈值设「错误率 + 延迟」双指标,单看一个会误剔。
面试速答
- 为什么轮询不行?推理请求代价方差百倍,轮询会让长请求堆积、短请求饿死,且破坏 KV Cache 局部性。
- Least-Queue 看什么?不只看 inflight 数量,看「剩余待生成 token 工作量」,选最快腾出容量的实例。
- Prefix-Affinity 解决什么?同前缀请求落同实例命中 prefix cache,省算力稳 TTFT。
- Cost-Aware 风险?依赖长度预估准确度,预估失准就退化成 Least-Queue。
- 混合路由怎么做?先前缀亲和缩候选集,集内 Least-Queue/Cost-Aware 选最空,全过载则放宽全局并牺牲前缀缓存保 SLA。
- 网关还要做什么?全局并发上限、租户限流、优先级队列、实例熔断。
高频追问清单
- 前缀亲和和一致性哈希有什么区别?(前者按语义前缀保缓存局部性,后者按 key 均匀分布,目的不同)
- 实例扩缩容时 prefix cache 映射怎么迁移,会冷启动吗?(映射表失效 + 请求回退重算前缀,冷启动短暂升高 TTFT)
- 多租户下怎么既限流又保 SLA?(租户级令牌桶 + 全局容量 + 优先级队列 + 隔离)
- 路由决策延迟本身会不会成为瓶颈?(决策在网关内存完成,微秒级,远低于 GPU 计算,可控)
- 没有 RDMA 的分离集群,路由要额外考虑什么?(KV 传输走 TCP 慢,路由要尽量避免跨节点 KV 搬运,倾向 prefill/decode 同可用区)
- prefix cache 命中率怎么量化并用作路由指标?(命中率 = 跳过计算 token / 总 token,低则意味路由散了,应强化前缀亲和)