← 返回
IT技术

模型过载时,请求排队与延迟到底发生了什么

✍️ zhirenhun 📅 2026/10/31 👁 51 阅读 ⏱ 30 分钟
模型过载时,请求排队与延迟到底发生了什么

在 DigitalOcean 上对 LLM 推理进行负载测试,模拟 2 倍、5 倍和 10 倍的突发流量。测量有效吞吐量、队列等待时间、P99 延迟和恢复时间。

一篇爆款文章、一次产品发布,或突然涌入的 Agent 流量,都可能在几秒内让你的 AI 应用请求量成倍增长。批处理能吸收峰值的第一波冲击。但之后,请求到达的速度超过 GPU 的处理速度:队列变长、截止时间超时、重试又带来更多负载。一分钟的突发流量,可能让用户在流量恢复正常后仍要等待好几分钟。大多数基准测试忽略了这一点,因为它们报告的是每秒峰值请求数,而不是有多少用户及时得到了回答。

在本教程中,你将在 DigitalOcean Serverless Inference、Dedicated Inference,或 GPU Droplets / DOKS 上自管理的引擎(如)上对 LLM 推理进行负载测试。你将把容量定义为在每请求截止时间约束下的有效吞吐量,并用开放到达负载生成器驱动独立的 2 倍、5 倍和 10 倍突发流量。你还会梳理请求在何处等待,用积压计算预测恢复时间,测量自动扩缩容从触发到新容量真正开始服务流量的耗时,并如实报告 P50、P95 和 P99。

理解突发流量期间发生了什么

突发流量首先提升硬件利用率,随后将其压垮。批处理让更多请求共享每个 GPU 步骤,因此吞吐量起初会上升。一旦超过有效容量阈值,任务到达的速度就快于完成的速度:队列老化、截止时间超时,客户端重试又叠加到原始负载之上。

仅凭请求数量无法预测这个临界点。一个 LLM 请求包含两个阶段。prefill 阶段一次性处理整个提示词;decode 阶段则逐个 token 生成输出。长提示词消耗 prefill 时间,长回答消耗 decode 时间,因此一个 4,000 token 的检索提示词加上长回答,所需的工作量远超一次短分类任务。

在三个阶段测量负载

RPS 指每秒请求数。在三个点上跟踪它:

假设你的生成器计划 100 RPS,但实际只发出 90。服务每秒完成 60 个有效响应,其余要么失败,要么仍在运行。在这 60 个中,只有 45 个在你的应用截止时间之前完成。这每秒 45 个就是你的有效吞吐量:服务实际交付的有用容量。

把有效吞吐量(goodput)作为容量指标,并把它与请求 RPS、发送 RPS、失败数和延迟并列报告。一个快速拒绝请求的服务可能看起来很快,但实际上只服务了极少数用户。

选择你要测试的 DigitalOcean 部署方式

突发测试能告诉你什么,取决于部署方式,因为每种方式暴露的控制项和遥测数据各不相同。DigitalOcean 提供无服务器推理和专用推理;第三条路径是在 GPU Droplets 或 DigitalOcean Kubernetes(DOKS)上运行你自己的推理服务器。

部署方式 你可控制 你可观测 突发测试衡量的内容
无服务器推理 仅客户端行为 客户端计时、状态码、配额响应头 测试时你的账户、模型和工作负载可用的容量
专用推理 模型、GPU 方案、加速器数量、端点访问 客户端计时以及部署状态 该部署配置的容量
自管理(GPU Droplets 或 DOKS) 推理引擎、批处理、并发、扩缩容策略 引擎指标、追踪、扩缩容事件 各个队列在何处形成,以及每项配置变更的效果

无服务器推理通过 https://inference.do-ai.run 上的 OpenAI 兼容 API 隐藏了 GPU 管理。你的应用仍需自行实现准入控制、超时截止和有界重试。

无服务器配额同时覆盖请求数和 token 数,并因账户层级和模型而异;当前数值见 推理限制。请把公布的限额视为共享容量的上限,而非保证吞吐量:低于这些限额的请求仍可能被排队、限流或拒绝。因此,无服务器基准测试衡量的是你的账户在当时获得的容量,而不是底层 GPU 的最大吞吐量。

专用推理会把模型部署到你选定的 GPU 资源上。默认情况下,它只在你 VPC 网络内暴露一个私有端点;创建时可以选择启用公共端点。私有端点需要使用你从部署概览页下载的 CA 证书。

记录你测试过的模型、GPU 方案、加速器数量和端点。专用硬件资源同样有限:不要以为它会自动提供突发扩容、无限并发或稳定的 P99 延迟。

自管理推理

自己运行推理引擎,比如在 GPU Droplet 或 DOKS 上跑 vLLM,你就能掌控调度、批处理、并发、监控埋点和扩缩容。当然,这些决策也由你负责。走这条路,你能清楚看到请求在哪里排队,也能测试服务配置改动带来的影响。

梳理请求的等待环节

一个请求至少可能在四个队列中等待,而增加 GPU 只能缩短最后一个队列。请求依次经过:

  1. 你的应用,可能因并发上限而让请求等待。
  2. HTTP 客户端,可能在等待可用连接。
  3. 网关,可能因准入控制或速率限制而延迟请求。
  4. 推理引擎,可能在批处理前把已接收的请求排队。

在 Agent 和 RAG 流水线中,检索服务和下游工具还会引入各自的队列。测试前先列出请求路径上的每个队列,让每个队列都有独立的时间戳。增加 GPU 只会让最后一个阶段变快;它前面的四个队列各自都需要时间戳。

首 token 时间与完成时间

TTFT 要从两个时间点分别测量:原始应用请求到达时,以及你的代码真正发出推理请求时。两者之差就是应用内部的延迟。完成延迟要单独跟踪,因为首 token 来得快,不代表完整响应就快。

对于流式响应,只统计第一个内容 token。HTTP 头、keepalive 和仅含角色的数据块都不是模型生成的回答文本。还要记录 token 之间的间隔:响应开头很快、随后卡住,体验依然很差。

不要用相减的方式推算排队时间

不要用空闲请求的延迟减去繁忙请求的延迟,来估算服务商的排队时间。预填充时间、批处理、缓存状态、网络延迟和输出长度在负载下都会变化。没有内部遥测数据时,请把这一差值称为额外观测延迟,而不是实测排队等待时间。

以有效工作量定义容量

容量是指针对特定工作负载,服务在满足延迟、可靠性和质量目标的前提下,能够持续承受的最大请求到达速率。如果大多数响应在用户放弃之后才到达,那么每秒最大 HTTP 请求数并不等于容量。

把单请求截止时间与服务目标分开

有效吞吐(goodput)按单个请求计数,因此需要一个能判定单个请求通过或失败的测试。百分位目标描述的是整体分布,应归入单独的服务级别目标(SLO)。两者都要定义:

以上数字只是随意举例。请根据产品选择合适截止时间:聊天界面能接受的时限比文档摘要工具更宽松。

测试贴近真实的工作负载画像

容量取决于输入长度、输出长度、数值精度、批处理和并发设置。至少测试三种画像:短对话请求、带长上下文的检索提示词,以及长生成请求。记录每个响应中实际的 token 数;请求里写的 max_tokens 并不能说明模型实际生成了多少 token。

每次运行都要记录模型 ID 与版本、部署类型、硬件(已知时)、服务配置、提示词集以及缓存状态。对于外部托管的模型,未披露的细节标记为不可用。仅凭参数规模无法解释实测吞吐量的差异。

找到可持续基线

1. 逐步提高施加的负载,直到某个受监控的 SLO 失败。

2. 再逐步回调,找到能稳定运行的最高负载水平。这就是你的可持续容量。

3. 在该水平以下选一个正常流量负载,作为基线。

4. 报告所有突发时,同时给出绝对 RPS 和相对基线的倍数。“10×”若不说清放大的是哪个基线,就毫无意义。

设计突发实验

每次运行都采用相同的四个阶段,这样 2×、5×、10× 的结果可以直接比较。一个实用的初始时间安排:

阶段 时长 到达速率 目的
预热 2 分钟 基线 填充连接池和缓存;丢弃此阶段结果
基线 5 分钟 基线 正常负载下的参考延迟与有效吞吐
突发 1 分钟 基线 × 倍数 被测的流量尖峰
恢复 ≥ 5 分钟 基线 观察积压排空、延迟回落到基线

各次运行之间遵循以下规则:

  • 每个倍数单独运行。绝不在同一实验里连续发起多次突发:前一次突发积压的队列会污染下一次。每次运行前,确认延迟、队列深度和副本数都已回到基线。
  • 必要时延长恢复阶段。只有当 P99 连续三个间隔保持在突发前基线的 10% 以内,才算延迟已恢复。若到窗口结束时仍未恢复,就延长窗口,并报告未能在最初选定的窗口内恢复。
  • 重复并打乱顺序。每个场景至少执行三次,并变换倍率顺序,避免渐进漂移反复偏向某个倍率。报告各次运行 P99 延迟的中位数和分布范围。三次试验足以暴露波动性,但不足以给出精确的 P99。
  • 增加持续过载运行。将突发速率维持至少 5–10 分钟,长于自动扩缩容的反应时间,以观察队列增长、内存压力、错误率和扩缩容行为——这些在 1 分钟尖峰中不会出现。
  • 每个阶段都要测量:提供的负载、发送的请求、接受的请求(可见时)、成功完成数、失败数以及在途工作量。这些数据合在一起,才能看出服务是接受了需求、拒绝了需求,还是仅仅推迟了后果。

    使用开放到达负载生成器

    负载生成器必须在服务变慢时仍按计划持续发送请求,否则就会掩盖你试图测量的积压。

    开放到达与封闭到达

    在封闭工作负载中,固定客户端池里的每个客户端都要等上一个请求完成后才发送下一个请求。开放工作负载则提前很久安排到达时间,与完成情况无关。真实用户启动应用时正是这样操作的。选定一种到达模式,记录下来,并在各部署中复用:

    现成的工具也能做到这一点。k6 的 constant-arrival-rate、ramping-arrival-rate 执行器以及 Vegeta 都能产生开放到达。许多简单脚本以及 Locust 默认的用户模型则是封闭的。在相信某个工具给出的数字之前,先核实清楚。

    生成器规则

    如实测量队列等待时间

    在每个队列各自的边界上测量。仅靠客户端计时无法得知引擎将请求排队了多久。

    引擎队列

    如果用的是自建引擎,就直接采用引擎原生指标。以 vLLM 为例,它在生产环境指标文档中暴露了以下 Prometheus 指标:

    指标 衡量内容
    vllm:request_queue_time_seconds 请求在引擎中等待调度的时间(直方图)
    vllm:time_to_first_token_seconds 引擎侧 TTFT(直方图)
    vllm:e2e_request_latency_seconds 引擎侧端到端延迟(直方图)
    vllm:num_requests_waiting 当前在引擎中排队的请求数(仪表)
    vllm:num_requests_running 当前正在批处理中的请求数(仪表)

    指标名称和直方图分桶会随版本变化,搭建仪表盘前,请对照实际部署的版本确认。

    应用层队列

    应用层排队应与引擎排队分开度量。网关准入阶段的时间戳加上 worker 分发时间戳,共同描述应用层队列。引擎遥测则包含描述自身调度边界的指标。分布式追踪可以把两者串联起来,但比较不同机器的时戳时,时钟同步至关重要。

    托管端点

    DigitalOcean 的 serverless 指标涵盖吞吐量、错误率、TTFT 和端到端延迟。这些数字可用于比较不同服务,但并不能揭示内部每个调度阶段。对于 serverless 和 Dedicated Inference,应报告客户端观测到的延迟,并将负载下增加的延迟称为额外观测延迟。

    持续跟踪队列深度与等待时长

    把队列深度画成时间序列,而不是只看平均值:平均值会掩盖稳定队列与无限增长的积压之间的差别。同时还要跟踪队列中最老请求的等待时长。深度只反映请求数量,不反映它们等了多久:如果调度器优先处理某些请求,队列可能一直很短,但少数请求的等待时间会远超其他请求。

    在测试前预测积压与恢复

    一分钟的突发流量,可能造成五分钟的服务降级。这一点无需发出任何请求就能提前预测。设 λ 为准入到达率,μ 为有效处理能力(单位均为请求/秒)。假设已存在积压,积压量的变化速度约为 λ − μ 请求/秒。

    示例计算

    某服务每秒处理 10 个请求(μ = 10),正常需求为每秒 8 个。一次持续 60 秒的突发使到达率达到每秒 20 个请求,起始队列为空:

    当到达率回落到每秒 8 个时,正常流量占用了大部分处理能力,只剩每秒 2 个请求用于消化积压:

    这一分钟的突发,代价是之后五分钟的高延迟。如果突发结束的瞬间处理能力提升到每秒 16 个请求,就有每秒 8 个的余量用于排空积压,恢复时间为 600 ÷ (16 − 8) = 75 秒。

    模型的局限

    这些估算假设各请求成本相近、处理能力稳定,且没有因拒绝、取消或超时而移除的工作。快速恢复的假设是突发停止时处理能力能立即到位。实际恢复情况会随批处理效率、输出长度和可用容量的变化而不同,而且超时并不总能阻止服务端继续执行工作。可以用上面的公式设定预期,再用队列深度、完成率和恢复时间来验证。有两个结果最关键:

    持续度量自动扩缩容,直到容量真正有用

    自动扩缩的耗时统计,终点应是新容量成功处理真实请求,而不是扩缩决策触发那一刻。扩缩决策、节点就绪、模型副本开始处理流量,是三个独立的里程碑;只统计第一个,得到的数字看着让人安心,实则毫无用处。

    每次扩缩事件都要记录各里程碑的时间戳:

    有些阶段会重叠,所以要使用真实事件时间,而不是把各阶段平均耗时相加。要报告多次扩缩事件的分布,而不是单次运行的结果。

    DigitalOcean Kubernetes

    DOKS Cluster Autoscaler 根据 Pod 能否被调度来增减节点。它扩缩的是节点,不是应用副本。自管理的推理服务还需要一个副本控制器,比如 Horizontal Pod Autoscaler(HPA)或 KEDA,用来增加推理 Pod;当这些 Pod 无法调度时,再由 Cluster Autoscaler 增加节点。DigitalOcean 的 HPA 与 Cluster Autoscaler 配合教程 演示了两者如何协同工作。

    扩缩要依据能反映推理负载的信号,比如通过自定义指标适配器暴露的 vllm:num_requests_waiting,而不是 CPU 或 GPU 利用率。GPU 可能在队列开始增长之前很久就显示高利用率,并且在队列暴涨时仍保持高位。

    Dedicated Inference

    对于 Dedicated Inference,要先确认你的配置实际提供哪些扩缩控制,不要想当然认为 Kubernetes 的行为一定适用。部署的创建需要时间,而且区域的 GPU 可用性可能成为限制。设置加速器数量是一种容量选择,并不能证明具备自动突发扩缩能力。

    Serverless Inference

    无服务器端点的内部副本事件不可见。请根据你自己的测量结果报告观察到的适应或恢复时间,而不是推断出的实例启动时间。 无论你测试哪条路径:如果 60 秒的突发在新容量变得有用之前就结束了,那么自动扩缩容对这次突发没有帮助。热余量、准入控制或延迟工作必须覆盖这个缺口。

    分析结果并解读 P50、P95 和 P99

    把 P50、P95 和 P99 放在一起看,并且始终与错误率和超时失败率并列。P50 是中位数;P95 和 P99 描述分布中越来越慢的部分。

    只有当你清楚每个数字统计了什么,它才有意义:模型、工作负载特征、阶段、成功条件,以及延迟是从计划到达时间开始算,还是从派发时间开始算。

    诚实百分位数的规则

    诊断各倍率下会出什么问题

    负载达到正常值的2倍时,要判断吞吐量是否按比例扩展、延迟目标是否仍能满足。有些应用能高效利用闲置容量,有些则立刻越过运行边界。如果不知道基线利用率和负载构成,光看倍率无法预测属于哪种情况。

    到5倍时,留意吞吐饱和的迹象,比如队列深度和TTFT上升。检查token配额、连接数限制、应用并发、引擎等待请求数和可用内存。如果完成率趋于平稳而请求仍在增长,说明存在瓶颈,但要找出瓶颈在哪一层。

    到10倍时,要刻意观察故障行为。系统是立即拒绝超额需求,还是积压过期任务,或者开启重试反而放大过载?交互请求是否变慢,同时大输出占用容量?还要确认压测工具确实达到了设定的请求到达率。

    结果整理成表格,每个模型、负载、部署和倍率各占一行。列出提供RPS、派发RPS、成功RPS、有效吞吐、TTFT分位数、完成时间分位数、实测队列等待、拒绝率、超时率和恢复时间。无法获取的指标要明确标注。

    重点看指标之间的关系,而非孤立数值。TTFT上升而生成速度稳定,说明存在等待或prefill压力;TTFT稳定但完成变慢,说明生成阶段有争抢或输出更长。这些只是诊断假设,下结论前要用token分布和服务端遥测数据验证。

    结论

    一次爆红流量对AI应用是助力还是伤害,不只取决于模型速度。请求到达模式、配额执行、队列上限、扩缩容延迟、重试策略和闲置容量,共同决定用户能否及时得到回答,还是会撞上比流量高峰持续更久的积压。

    本教程中,你把容量定义为在单请求截止时间内的有效吞吐,搭建了开放到达的压测工具,分别跑了2倍、5倍、10倍三波独立突发流量并包含恢复阶段。你还区分了应用队列和引擎队列,用积压计算预测恢复时间,并看到有界队列如何在突发期间让有效吞吐几乎翻倍。

    在DigitalOcean上,选择实测行为符合你的负载和团队运维能力的部署方案。然后用数字回答生产就绪问题:你能按时服务多少需求,超出的部分会怎样,服务多久能恢复正常?

    参考来源

    ——

    广告 广告
    🧑‍💻

    zhirenhun

    一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。