27届大模型面试准备(七十):大模型推理服务的负载均衡与智能请求路由
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 路由(带剩余工作量估计)
def least_queue_route(req, instances):
best, best_score = None, float("inf")
est_out = estimate_output_len(req) # 预估本请求输出长度
for inst in instances:
load = inst.inflight_seqs * avg_decode_cost \
+ inst.remaining_tokens # 已排队剩余 token
score = load + est_out * decode_cost(inst)
if score < best_score:
best, best_score = inst, score
return best
4. 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 显存占用。路由选「加入后负载仍最低」且「不超显存上限」的实例。
def cost_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")
for inst in instances:
if inst.free_kv_mem < kv_mem: # 显存硬约束
continue
util_after = (inst.busy_compute + p*prefill + o*decode) \
/ inst.capacity
if util_after < best_util:
best_util, best = util_after, inst
return best or fallback_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 请求插队,但要对低优先做公平性保护,避免饿死。
- 熔断:某实例错误率/延迟超阈值,路由暂时剔除它(呼应七十下篇智能体侧同理)。
# 伪代码:带优先级与熔断的路由网关
class RouteGateway:
def route(self, req):
if self.rate_limiter.deny(req.tenant): # 租户限流
return RESP_TOO_MANY
insts = self.candidates(req) # 前缀亲和集合
insts = [i for i in insts if not self.cb.open(i)] # 剔除熔断实例
if not insts:
insts = [i for i in self.all if not self.cb.open(i)]
target = self.hybrid_select(req, insts) # Least-Queue/Cost-Aware
self.cb.record_attempt(target)
return target
8. 生产落地 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,低则意味路由散了,应强化前缀亲和)
更多推荐
所有评论(0)