Agent 如何支撑 3 万 QPS:从单实例到分布式推理的工程全景
普通接口支撑 3 万 QPS 只需要一堆无状态节点,而 Agent 支撑 3 万 QPS,本质上是在为一个「每请求会调用 3~10 次 LLM 推理」的系统做容量工程。
一、先把账算清楚:3 万 QPS 意味着什么
在动手设计架构之前,必须先回答一个问题:Agent 的 QPS 和普通 API 的 QPS 根本不是同一个量纲。
一个典型的 ReAct 风格 Agent,处理一次用户请求通常要经历:
用户Query → 意图路由 → 规划(LLM) → 工具调用 → 观察整理(LLM) → 检索(向量库) → 总结(LLM) → 输出
假设一次用户请求平均触发 3 次 LLM 推理调用,每次推理平均生成 500 个 token,那么:
| 指标 | 计算 | 数值 |
|---|---|---|
| 用户侧 QPS | 需求 | 30,000 |
| LLM 侧推理 QPS | 30,000 × 3 | 90,000 |
| 输出 token/秒 | 90,000 × 500 | 45,000,000 |
| 输出 token/分钟 | 45,000,000 × 60 | 27 亿 |
| 单卡输出能力 | 以 H100 实测约 4,000~6,000 tok/s 计 | ~5,000 tok/s |
| 推理卡需求量 | 45,000,000 ÷ 5,000 | 9,000 张 |
这就是 Agent 高并发的第一性矛盾: 我们以为在解决「请求调度」问题,实际上是在解决「GPU 算力」问题。3 万 QPS 的 Agent 系统,即使按最激进的推理优化,也需要千卡级别的推理集群。任何不谈算力的 QPS 方案都是空中楼阁。
因此,支撑 3 万 QPS 的工程主线只有一条:想尽一切办法降低「每次用户请求消耗的推理 token」,同时让每一张 GPU 的单位时间产出最大化。
二、容量建模:先做预算,再谈架构
2.1 从业务指标反推资源
容量规划必须从业务侧开始,逐层反推:
业务目标:3 万 QPS 用户请求
→ 每请求平均 LLM 调用次数 N(目标:从 5 压到 2.5)
→ 每调用平均输入 token(目标:从 8K 压到 3K)
→ 每调用平均输出 token(目标:从 800 压到 400)
→ 得出总 token 吞吐 = QPS × N × (input + output)
这三组数字(N、输入长度、输出长度)就是 Agent 高并发的三个可优化的自由变量,比堆 GPU 重要得多。工程上的所有手段,最终都是围绕它们做文章:
- 减少 N:合并多步推理(见 4.2)、缓存整条执行链
- 减少输入:上下文剪枝、前缀缓存、检索 top-k 收敛
- 减少输出:输出格式约束、更小的规划粒度、强制结构化输出
2.2 延迟与并发的互斥关系
容量建模还必须同时考虑延迟约束。假设产品要求 P95 延迟 3 秒,单次 LLM 调用平均耗时 1 秒,那么:
- 串行 3 次调用需要 3 秒 → 不允许任何额外的排队时间;
- 这意味着推理集群的利用率不能超过 60%~70%,必须预留缓冲,否则队列时延会瞬间击穿 P95 目标。
结论: 支撑 3 万 QPS 的同时满足延迟约束,实际需要的算力是「纯吞吐计算值」的 1.3~1.5 倍。容量预算里必须显式预留这 30%。
三、推理服务层:吞吐的胜负手
Agent 的推理请求有很强的结构性,可以针对性地优化。
3.1 请求特征与优化空间
| Agent 推理请求特征 | 对推理的影响 | 优化手段 |
|---|---|---|
| 输入远大于输出(上下文 5K~30K,输出几百 token) | 前缀计算占绝对主导 | Prefix Caching / KV Cache 复用 |
| 同一个 Prompt 模板高频复用 | 大量重复计算 | 模板级前缀缓存、按模板预填充 |
| 请求到达具有突发性 | 队列抖动、GPU 利用率低 | Continuous Batching + 动态批大小 |
| 单次 Agent 循环中多次调用相似上下文 | 上下文重叠 | 跨请求 KV Cache 共享 |
3.2 核心引擎选型:Continuous Batching
传统静态批处理(Static Batching)以「批」为单位整体等待,批内最慢的请求拖累整批,GPU 利用率极低。Continuous Batching(连续批处理,vLLM 首创并普及)按 token 粒度调度:某个序列生成完就立刻让出槽位,新请求随时插入。同一时刻一个 GPU 上可以混合执行处于不同阶段(预填充/解码)的多个请求。
工程上直接选择成熟方案,不要自研:
- vLLM:生态最成熟,PagedAttention + Continuous Batching,支持 Prefix Caching 与多种量化
- TensorRT-LLM:NVIDIA 官方,推理性能上限最高,但工程复杂度高
- SGLang:RadixAttention 前缀缓存非常契合 Agent 的模板化请求
3.3 Prefix Caching:Agent 场景的「复利」
Agent 请求中,系统提示词、工具描述、Few-shot 示例往往占输入的大半,且跨请求高度重复。Prefix Caching(vLLM 的 --enable-prefix-caching)让相同前缀的 KV 不再重复计算:
- 命中时,预填充阶段从毫秒级计算变为纯内存拷贝;
- 在 Agent 场景,输入 token 的成本通常可下降 40%~70%。
更进一步的思路是按 Agent 类型拆分前缀:
/cache/v1/planner-prompt → 规划 Agent 的公共前缀(KB 级,长期复用)
/cache/v1/retriever-prompt → 检索 Agent 的公共前缀
/cache/v1/qa-prompt → 问答 Agent 的公共前缀
对高频 Agent 模板,甚至可以预填充:服务启动时预先计算公共前缀的 KV,请求到达时直接拼接用户部分,把首 token 时间再压一个数量级。
3.4 模型与服务拆分:大小模型分层
不要让所有请求都走最大的模型。按请求难度分层路由:
- 路由/意图识别:7B
14B 小模型(12 张卡即可支撑上万 QPS 的纯分类任务) - 规划/工具选择:14B~32B 中模型
- 最终生成/复杂推理:70B+ 大模型,仅对需要深度推理的请求启用
配合投机解码(Speculative Decoding):小模型草稿 + 大模型验证,在输出 token 上通常可获得 1.5~2.5 倍加速,且不损失精度。
四、Agent 运行时层:让「会话」可以水平扩展
4.1 无状态化:Agent 的十二要素
LLM 推理天然无状态,但 Agent 有状态——执行栈、工具结果、历史消息都在运行内存里。要让 Agent 水平扩展到几千个副本,必须把状态外置:
Agent 实例(无状态,可随时销毁重建)
├── Session 状态 → Redis Cluster(TTL 管理,如 10 分钟无操作回收)
├── KV Cache 状态 → 推理服务内部(Prefix Cache,跨请求复用)
├── 工具调用结果 → 结果缓存(带失效策略)
└── 任务队列 → Kafka / Redis Stream(削峰 + 背压)
设计约束:
- 每次请求幂等:Agent 步骤必须支持「重试即重放」,不能因为实例重启丢上下文;
- 状态 TTL 化:所有会话状态必须有生命周期,否则内存和 Redis 会持续膨胀;
- 工具调用纯函数化:写操作(下单、发消息)与读操作分离,重试只对读操作生效。
4.2 减少调用次数:把串行循环改成并行 DAG
3 万 QPS 下,每减少一次 LLM 调用,就是减少几千张卡的负载。典型的优化:
串行(4 次 LLM 调用):
规划 → 检索 → 读文档 → 总结
并行 DAG(2 次 LLM 调用 + 并行工具):
规划(产出工具清单)→ [检索 ‖ 读文档 ‖ 查天气] 并行 → 汇总生成
LangGraph / 自研状态机都支持并行分支,代价是编排复杂度,收益是LLM 调用次数直接减半。此外还有更激进的方案:
- 执行链缓存:对「相同意图 + 相同参数」的请求,缓存整条 Agent 执行结果(不只是 LLM 输出);
- 计划复用:同一类任务(如「查天气」「查订单」)的规划结果高度相似,规划步骤命中模板时直接跳过 LLM。
五、缓存体系:在 LLM 之前的四道防线
高并发系统里,缓存的目标是让流量尽量不触达最贵的组件。Agent 系统的缓存分层如下:
| 层级 | 缓存内容 | 命中率期望 | 说明 |
|---|---|---|---|
| L1 响应缓存 | 完整 Agent 执行结果 | 10%~30% | 相同问题的重复请求,直接返回 |
| L2 语义缓存 | 相似问题的结果复用 | 5%~15% | 向量化相似度匹配(如 >0.95 判定重复) |
| L3 规划缓存 | 任务规划步骤 | 20%~40% | 相同任务模板跳过规划 LLM |
| L4 工具缓存 | 工具调用结果 | 30%~60% | 检索结果、API 返回按 key 缓存 |
| L5 前缀缓存 | KV Cache | 40%~70% | 推理层内部,见 3.3 |
执行顺序: 请求到达 → L1/L2 命中直接返回(不产生任何推理成本)→ L3 命中跳过规划 → L4 命中跳过工具 → L5 命中降低推理计算量。
一个容易被忽略的细节:语义缓存必须和新鲜度策略配合。股票行情、天气这类时效性数据不能命中缓存;可以用「TTL 按数据类别区分」+「缓存键包含时间窗口」来解决。
六、流量治理与弹性伸缩
6.1 入口与背压
3 万 QPS 的流量不可能均匀到达。入口层必须做:
- 网关限流:令牌桶 + 按用户/按 Agent 类型分级配额,超限返回 429 而非把压力传导给推理集群;
- 队列削峰:突发流量进入 Kafka / Redis Stream,消费者按推理集群的实际吞吐拉取,实现天然背压;
- 流式返回:SSE 流式输出让首字节时间(TTFB)大幅下降,用户感知的「响应」远早于完整生成。
6.2 弹性伸缩策略
推理集群的扩容不是即时的(拉起一个带权重加载的推理实例需要几十秒到几分钟),所以:
- K8s HPA 基于自定义指标(排队长度、GPU 利用率、token 吞吐),而非 CPU;
- 预留缓冲池:常备 10%~20% 的 warm 实例应对突发;
- 推理实例按模型类型分池:小模型池、大模型池独立伸缩,避免互相挤占;
- 降级预案:峰值超过容量的 120% 时,自动将请求降级到小模型 + 缓存命中模式,保证核心链路可用。
七、可观测性:3 万 QPS 下的容量会计
高并发系统最怕「黑盒」。Agent 场景建议建立以下指标,缺一不可:
业务层:用户QPS、按Agent类型QPS、P50/P95/P99 端到端延迟、缓存命中率
执行层:每请求LLM调用次数、每请求token消耗、工具调用耗时
推理层:TTFT、ITL(token间延迟)、GPU利用率、队列深度、KV Cache命中率
成本层:每百万token成本、每请求成本、按Agent类型的成本分布
重点追踪两个成本杠杆指标:
- 平均每用户请求的 LLM 调用次数 N——这是成本优化的总抓手,任何架构调整都应以 N 的下降为目标;
- 每请求平均输入 token——它与 Prefix Cache 命中率直接相关,是推理集群负载的先行指标。
八、落地路线图:分四步走
支撑 3 万 QPS 不是一次重构能完成的,建议按以下节奏推进:
第一步(0→1 万 QPS):
- 无状态化 Agent + Redis 会话存储
- vLLM 推理集群 + Continuous Batching + Prefix Caching
- 网关限流 + SSE 流式输出
- 大小模型分层路由
第二步(1 万→2 万 QPS):
- 响应缓存 + 语义缓存 + 工具缓存
- Agent 执行链并行化(串行改 DAG)
- 意图模板化,规划步骤缓存
第三步(2 万→3 万 QPS):
- 推理集群精细调优(投机解码、量化、张量并行/流水并行合理配比)
- 前缀缓存精细化(按 Agent 模板预填充)
- 队列削峰 + 弹性伸缩 + 降级预案
- 全链路追踪(OpenTelemetry)与成本看板
第四步(持续):
- 每一轮优化都以「每请求 token 消耗」为北极星指标
- 定期用压测验证:3 万 QPS 下 P95 延迟是否守住,GPU 利用率是否达到 70%+
九、关键结论
- 3 万 QPS 的本质是 token 吞吐问题,不是请求调度问题;先算清「每请求消耗多少 token」,再决定买多少卡。
- 推理层用成熟引擎(vLLM/SGLang/TensorRT-LLM),把精力留给 Prefix Caching 和批处理调优,绝不自研推理内核。
- 状态外置是无状态化的前提,无状态化是水平扩展的前提;会话状态必须 TTL 化、步骤必须幂等。
- 缓存是成本最大的杠杆:五级缓存体系 + 执行链缓存,能让真实的 LLM 负载比 QPS 数值低一个数量级。
- 用「每请求 LLM 调用次数」和「每请求输入 token」两个指标管理容量,它们比 GPU 数量更能反映系统健康度。
支撑 3 万 QPS 的 Agent 系统,最终拼的不是某一次优化,而是把「少算、快算、缓存、复用」贯穿到每一层架构决策中。