文章

LLM Serving 过载保护实战:用 SLO 驱动自动伸缩与准入控制

大模型推理服务上线后,真正棘手的问题往往不是单次请求慢,而是流量突增时系统陷入不可恢复的排队状态。本文围绕 SLO 驱动的自动伸缩与准入控制,给出基于队列长度、Token 压力、TTFT/TPOT 和租户配额的过载保护工程落地清单。

背景问题:LLM 服务最怕的不是慢,而是失控

大模型推理服务上线后,团队很容易把优化重点放在单次请求延迟、吞吐或 GPU 利用率上。但到了真实生产环境,最棘手的问题往往不是”某一次请求慢”,而是流量突增时系统进入不可恢复的排队状态

典型现象包括:

  • 请求入口还在持续接收流量,但后端 GPU 队列已经堆积。
  • P50 延迟看起来可接受,P95/P99 已经快速恶化。
  • 自动扩容已经触发,但新副本冷启动、权重加载、模型初始化赶不上流量峰值。
  • 客户端重试进一步放大流量,形成 retry storm。
  • 最终不是少量请求失败,而是大面积请求一起超时。

因此,LLM Serving 的生产治理不能只问”如何提升吞吐”,还要问:当容量不够时,系统应该怎样有控制地变慢、排队、扩容、拒绝和恢复

本文的核心思路是:用 SLO 驱动的自动伸缩与准入控制 构建 LLM 推理服务的过载保护闭环。

核心原理:从资源指标切换到 SLO 与 Token 压力

传统 Web 服务常用 CPU、内存、QPS、平均延迟做扩缩容依据。但 LLM 推理有几个特殊性,使这些指标不够用。

请求成本高度不均匀

两个请求的 QPS 都是 1,但成本可能完全不同。一个请求可能只有几十个输入 Token 和几十个输出 Token;另一个请求可能有上万输入 Token,并要求生成长答案。对于 LLM Serving,真正消耗资源的是 Token 工作量、KV Cache、显存占用、解码步数和批处理调度空间。

因此,准入控制不能只按请求数计算。至少要把请求拆成:

维度影响
input_tokens影响 prefill 压力和 TTFT
max_output_tokens影响 decode 持续时间和 TPOT
tenant_id影响配额、公平性和成本归属
model_id影响权重加载、显存池和实例选择
priority区分交互式请求、后台任务和批量任务

GPU 利用率是滞后信号

GPU 利用率高不一定代表系统健康,低也不一定代表系统空闲。LLM 推理中,排队可能发生在入口、调度器、KV Cache 分配、网络传输、模型副本冷启动等多个位置。只看 GPU 利用率,往往要等问题已经发生后才知道。

Ray Serve 的官方文档也把自动伸缩建立在请求队列和 ongoing requests 上,而不是只看底层资源利用率。target_ongoing_requests 表示 autoscaler 希望每个 replica 维持的平均进行中请求数,该值需要结合请求处理时长和延迟目标调小或调大。

SLO 是保护策略的边界

LLM 服务常见 SLO 可以拆成三类:

指标含义保护重点
TTFTTime To First Token,首 Token 时间用户是否感觉系统卡住
TPOT / TBT每个输出 Token 的间隔流式输出是否顺滑
E2E Latency端到端完成时间长答案、批处理和后台任务体验

如果一个请求进入系统后必然超过 SLO,继续接收它并不一定更好。更好的做法是:在入口快速判断,能服务就服务,不能服务就降级、排队、返回 429/503,或者建议客户端稍后重试。

工程落地:构建五层过载保护闭环

第一层:入口限流,先控制流量进入速度

入口限流的目标不是简单拒绝请求,而是避免超过系统可恢复的负载范围。建议至少按以下维度做限流:

  • 全局 QPS 限制。
  • 单租户 QPS 限制。
  • 单租户 Token/min 限制。
  • 单模型并发限制。
  • 单请求最大输入 Token 和最大输出 Token 限制。
  • 高风险接口的更严格限流,例如 agent、tool calling、长上下文分析。

一个实用规则是:限流单位要从 request 升级到 token budget。例如:

tenant_policy:
  tenant_a:
    requests_per_minute: 600
    input_tokens_per_minute: 2_000_000
    output_tokens_per_minute: 800_000
    max_concurrent_requests: 80
    max_input_tokens_per_request: 32_000
    max_output_tokens_per_request: 2_000

这样可以避免一个租户用少量超长请求耗尽整个模型池。

第二层:准入控制,判断请求是否值得进入队列

准入控制比限流更接近推理服务本体。它不仅看租户额度,还要看当前服务状态:

  • 当前队列长度。
  • 估计等待时间。
  • 可用 KV Cache 或显存预算。
  • 当前模型池副本数。
  • 冷启动中的副本数。
  • 历史 TTFT/TPOT SLO 达成率。
  • 请求剩余 SLO 空间。

可以用一个简化的判断逻辑:

def admit(request, state):
    estimated_tokens = request.input_tokens + request.max_output_tokens
    estimated_wait_ms = state.queue_delay_ms_p95
    estimated_service_ms = estimate_service_time(request, state)
    predicted_latency = estimated_wait_ms + estimated_service_ms

    if request.tenant.remaining_token_budget < estimated_tokens:
        return "reject_quota"
    if state.kv_cache_free_blocks < estimate_kv_blocks(request):
        return "reject_memory_pressure"
    if predicted_latency > request.slo_ms:
        if request.priority == "background":
            return "defer"
        return "reject_fast"

    return "admit"

这里的关键不是估算绝对准确,而是避免在明显无法满足 SLO 时继续堆积请求。

第三层:队列分级,避免高优先级请求被长任务拖垮

生产系统不应只有一个 FIFO 队列。至少应区分:

  • 交互式请求:聊天、搜索、客服问答,对 TTFT 敏感。
  • 后台请求:批量摘要、离线分析、低优先级自动任务。
  • 高价值租户请求:付费等级更高,SLO 更严格。
  • 长上下文请求:Token 成本高,容易拖慢队列。

多租户场景下,还需要避免一个租户挤占所有容量。可以采用 weighted fair queue、priority queue、token bucket、deadline-aware queue 等策略。近期关于 multi-tenant GPU inference 的研究也指出,输出长度和运行时 Token 漂移会影响调度准确性,因此调度器需要运行时反馈修正,而不是只依赖请求进入时的估算。

第四层:自动伸缩,基于队列与 SLO 而不是单点资源指标

自动伸缩的输入建议至少包括:

  • 队列长度和排队时间。
  • ongoing requests per replica。
  • TTFT P95/P99。
  • TPOT P95/P99。
  • 每秒输入 Token、每秒输出 Token。
  • 过去几分钟 SLO 违约率。
  • 正在启动的副本数。
  • 冷启动时间分布。

Chiron 论文提出了基于 queue size、utilization 和 SLO 的 hierarchical backpressure,用于 LLM Serving 的自动伸缩;它的核心启发是:扩缩容策略要把请求类型和 SLO 压力纳入判断,而不是把所有请求都当成相同的 QPS。

对于 LLM 服务,可以把这些机制进一步扩展成:

autoscaling:
  min_replicas: 2
  max_replicas: 20
  target_ongoing_requests: 2
  max_ongoing_requests: 4
  scale_up_signal:
    - queue_delay_p95_ms > 800
    - ttft_p95_ms > 1500
    - input_tokens_per_second > baseline * 1.4
  scale_down_signal:
    - queue_delay_p95_ms < 200 for 10m
    - gpu_memory_pressure < 60% for 10m
    - slo_violation_rate < 1% for 10m

这里要避免两个误区:

  1. 扩容不是立即生效。大模型副本可能需要拉镜像、加载权重、初始化推理引擎、预热 CUDA kernel。
  2. 扩容不一定解决所有瓶颈。如果瓶颈在共享网关、KV Cache 迁移、租户配额或下游工具调用,单纯增加 replica 没有意义。

第五层:降级和快速失败,防止雪崩扩散

过载时不要只有”等”。可选策略包括:

  • 对低优先级请求返回 429,并带 Retry-After
  • 对后台任务进入延迟队列。
  • 缩短最大输出 Token。
  • 切换到更小模型。
  • 关闭高成本功能,例如多轮工具调用、深度推理、长上下文扩展。
  • 对相同请求做幂等缓存或结果复用。
  • 对客户端重试设置 retry budget,避免无限重试。

一个成熟系统应明确区分:

状态系统行为
正常接收请求,按优先级调度
轻度拥塞降低低优先级并发,提高扩容敏感度
中度拥塞部分租户限流,后台任务延迟执行
重度拥塞快速失败,保护核心交互请求
恢复阶段逐步放开限流,避免瞬间回流

适用场景

这种机制尤其适合以下场景:

  1. 对话类产品:用户对首 Token 延迟敏感,需要优先保护 TTFT。
  2. 企业多租户平台:不同客户有不同额度、优先级和成本归属。
  3. Agent 平台:请求可能触发工具调用、长链路推理和多次模型调用,成本波动大。
  4. 内部 AI 网关:多个业务共享同一模型池,需要配额和公平性。
  5. Serverless GPU 推理:冷启动和权重加载明显影响扩容速度。

常见误区

误区一:只要 GPU 利用率高就是好事

GPU 利用率高可能代表资源利用充分,也可能代表系统已经没有调度余量。LLM Serving 更应该关注 goodput,即满足 SLO 的有效吞吐,而不是单纯吞吐。

误区二:所有请求都应该排队等待

如果请求等待后必然超时,排队只会消耗资源并放大尾延迟。此时快速失败反而更利于用户体验和系统恢复。

误区三:自动扩容可以替代限流

自动扩容有延迟,且受 GPU 库存、模型加载、调度器和网络限制影响。入口限流、准入控制和自动扩容必须配合使用。

误区四:按请求数计费和限流就够了

LLM 请求的真实成本由 Token、模型、上下文长度、输出长度和推理策略共同决定。按请求数限流会低估长上下文和长输出请求的压力。

上线检查清单

指标层

  • 是否采集 TTFT、TPOT、E2E latency 的 P50/P95/P99?
  • 是否区分 input tokens、output tokens、tokens/sec?
  • 是否能看到 queue delay、running requests、waiting requests?
  • 是否能按 tenant、model、priority 维度拆分指标?
  • 是否记录 rejected、deferred、degraded 的原因?

策略层

  • 是否有全局、租户、模型三个层级的限流?
  • 是否有 token budget,而不是只按 QPS?
  • 是否定义交互式、后台、批处理请求的优先级?
  • 是否有快速失败策略和 Retry-After?
  • 是否有重试预算,避免客户端放大流量?

扩容层

  • 是否测过模型副本冷启动时间?
  • 是否设置 min replicas 避免完全冷启动?
  • 是否用队列和 SLO 指标触发扩容?
  • 是否设置 max replicas,避免无限扩容造成成本失控?
  • 是否区分扩容中、已就绪、不可用副本?

演练层

  • 是否做过突增流量压测?
  • 是否做过长上下文混入压测?
  • 是否做过单租户恶意或异常高负载测试?
  • 是否验证过限流恢复过程?
  • 是否验证过监控告警能定位到具体租户和模型?

参考资料

常见问题

LLM Serving 自动伸缩为什么不能只看 GPU 利用率?
GPU 利用率是滞后指标,流量突增时排队、首 Token 延迟和输出 Token 延迟可能已经恶化。生产系统更需要结合队列长度、Token 预算、TTFT、TPOT 和 SLO 达成率来决策。
准入控制和限流是不是会降低业务成功率?
短期看会拒绝部分请求,但它能避免所有请求一起超时。合理的准入控制应结合租户配额、请求成本预估、重试预算和降级策略,在容量不足时保护核心请求。
什么时候应该扩容,什么时候应该直接拒绝请求?
当排队压力可被扩容窗口吸收时应扩容;当冷启动时间超过请求剩余 SLO,或 GPU/模型池达到硬上限时,应优先限流、降级或快速失败。