当前位置: 首页 > news >正文

多模型 API 网关压测:并发、延迟与计费的三角平衡

多模型 API 网关压测:并发、延迟与计费的三角平衡

去年底我们系统只接了一个模型,没什么感知。今年陆续加了三个之后问题来了——同一个 Prompt,DeepSeek 返回 800ms,Kimi 那边能拖到 2 秒,各自的 Token 计费方式还不一样,月底看账单的时候心态直接崩了。后来在器灵模型广场上把四个模型统一接入,第一件事就是拉出来做一次完整压测——不搞清楚每个模型在高并发下到底什么表现,后面出问题就是线上的事故。

“QPS 够了就行”——这句话在单模型场景下勉强成立,在多模型 API 网关里是危险的简化。同样 100 QPS 的流量,打到 DeepSeek 和打到 Qwen 上,P99 延迟可以差 3 倍。同样的 Prompt,在 Kimi 上消耗的 Token 可能是 GLM 的 1.5 倍。网关做得好不好,不能只看能不能通——要看它能不能在高并发下,让延迟、吞吐和 Token 消耗三者同时可控。

这篇拆一次在器灵模型广场上的完整压测过程,从方法到数据,再到优化策略。


压测目标与环境

被测网关:一个聚合了 DeepSeek V4、Qwen3.6 Max、Kimi K2.7、GLM-5.2 四个模型的统一 API 网关。外部调用者使用同一套 Base URL 和 API Key,由网关根据model参数路由到对应的下游模型。压测跑在器灵模型广场的统一 API 网关上,调的就是平台聚合的这四个模型。

压测环境

参数
压测工具wrk2 + 自定义 Lua 脚本(长连接)
并发模型固定 RPS(Rate),逐步爬坡
请求 Prompt固定 200 Token 的翻译任务
输出限制max_tokens=150
网络同机房内网,RTT < 1ms
时长每轮 300s,预热 30s

指标采集:延迟(P50 / P95 / P99)、错误率、各模型实际 Token 消耗、连接池排队时长。数据来源是网关内置的 Prometheus metrics。


单模型延迟基准:先搞清楚每个模型有多快

压测的第一步不是测网关。先测每个下游模型的裸延迟——绕过网关,直接调各模型的官方 API,拿到基准线。后面网关多出来的每一毫秒延迟,都必须在基准线上能解释。

测试条件:单请求并发(1 RPS),Prompt 固定,max_tokens=150,连续跑 200 次取分布。

模型P50P95P99平均首 Token 延迟
DeepSeek V41182ms ± 286ms2734ms ± 512ms3618ms ± 742ms423ms ± 87ms
Qwen3.6 Max1537ms ± 341ms3182ms ± 604ms4092ms ± 891ms512ms ± 103ms
Kimi K2.72074ms ± 518ms4516ms ± 903ms5783ms ± 1245ms689ms ± 156ms
GLM-5.21831ms ± 429ms3922ms ± 751ms5108ms ± 1067ms574ms ± 132ms

三个观察:

  1. DeepSeek 在短 Prompt 下最稳定,P50→P99 的劣化幅度最小(1182ms→3618ms,约 3 倍),标准差也控制得最好,适合对延迟敏感的实时场景。
  2. Kimi 的长尾最严重,P99 达到 5783ms,标准差 1245ms 远超其他模型,说明偶发性的服务端排队或 GC 延迟波动很大。
  3. 首 Token 延迟 Qwen 和 Kimi 偏高,这意味着如果做打字机式流式渲染,用户体感会比 DeepSeek 慢半拍。

这条基准线是后续所有优化的参照系。网关增加的任何延迟,必须小于下游模型自身 P95 波动的量级——否则网关本身就成了瓶颈。


网关在不同 RPS 下的延迟分布

在网关层做 10→50→100→200 RPS 爬坡测试,请求均匀分配到四个下游模型(各 25%)。观察网关引入的额外开销。

wrk2 -t4 -c100 -d300s -R100 --latency \ -s chat_completions.lua \ http://gateway:8080/v1/chat/completions
RPSP50P95P99错误率网关 CPU
101406ms ± 312ms3074ms ± 618ms4012ms ± 852ms0%8%
501638ms ± 407ms3817ms ± 743ms5224ms ± 1061ms0%22%
1001921ms ± 493ms5083ms ± 1127ms7816ms ± 1834ms0.2%45%
2003184ms ± 1026ms11472ms ± 3802ms18253ms ± 6218ms2.1%78%

关键转折点在100 RPS → 200 RPS:P99 从 7816ms 飙升到 18253ms,错误率从 0.2% 跳到 2.1%。翻看网关日志,这个阶段的下游连接池开始出现排队——请求在网关内部等可用连接的时间超过了实际的模型推理时间。

结论:以当前配置(4 个下游,每下游 20 连接池),网关的安全水位在100 RPS 左右。超过这个量,瓶颈不在网关的处理性能,在连接池的排队深度。


连接池:被低估的延迟放大器

HTTP 连接池的配置直接影响尾延迟。两个极端:

  • 池太小:请求排队等连接,延迟飙升。
  • 池太大:下游模型被超额连接打满,触发限流或 OOM,反而更慢。

做一个对照实验:固定在 100 RPS,变化下游连接池大小(per downstream),看延迟和错误率的变化。

每下游连接数P50P99网关排队占比下游限流次数
55823ms ± 1406ms14208ms ± 3817ms72%0
102405ms ± 592ms9134ms ± 2602ms38%0
201921ms ± 493ms7816ms ± 1834ms15%0
401847ms ± 458ms8472ms ± 2051ms6%12
802106ms ± 713ms12263ms ± 3948ms3%47

最优区间在20-40 连接/下游。40 连接时 P99 反而略高于 20 连接——下游模型开始触发频率限制,部分请求被 429 拦截,重试拉高了尾延迟。到 80 连接时限制次数飙升,得不偿失。

实用规则:连接池大小 ≈ 目标 RPS × 模型平均响应时间 ÷ 下游实例数 × 1.5(留 50% 余量)。对本场景:100 × 1.92s ÷ 1 × 1.5 ≈ 29 连接,落在实测的最优区间内。


跨模型延迟方差:路由策略影响有多大

前面是均匀分配流量。如果把 100 RPS 全部路由到最快的 DeepSeek 和最慢的 Kimi,延迟分布差多少?

路由策略目标模型P50P99100 RPS 下的错误率
全部 DeepSeekDeepSeek V41317ms ± 304ms4235ms ± 961ms0%
全部 KimiKimi K2.72486ms ± 627ms7129ms ± 1804ms0.8%

同样 100 RPS,全跑 DeepSeek 的 P99(4235ms)只有全跑 Kimi(7129ms)的 59%。但 DeepSeek 也有自己的水位线——到 150 RPS 时 P99 跳到 6814ms,已经接近 Kimi 在 100 RPS 的水平了。

这引出一个工程决策:灰度路由。不是把所有流量扔给最快的模型,而是在延迟可接受的前提下,按模型当前的实时延迟做加权分配。伪代码:

defselect_model(models:list[ModelStats])->str:"""基于实时延迟的加权路由"""weights=[]forminmodels:ifm.error_rate>0.05:# 错误率超 5% 的模型直接降权weights.append(0.1)else:# 延迟越低权越高weights.append(1.0/max(m.p99_latency,0.1))total=sum(weights)probs=[w/totalforwinweights]returnrandom.choices([m.nameforminmodels],weights=probs)[0]

灰度路由让网关能自动把流量从变慢的模型上撤走,而不是等到报错再切——这对 P99 的改善比连接池优化更直接。


Token 计费和延迟的取舍:快不等于便宜

延迟最低的模型,Token 消耗不一定最低。同一个翻译任务在四个模型上的 Token 消耗:

模型输出 Token输入 Token总 Token 成本(按 2026.06 定价)P99 延迟
DeepSeek V4142215¥0.00324235ms ± 961ms
Qwen3.6 Max138215¥0.00484812ms ± 1073ms
Kimi K2.7168220¥0.00617129ms ± 1804ms
GLM-5.2145218¥0.00525926ms ± 1458ms

DeepSeek 最快也最便宜。但注意 Kimi 的输出 Token 比 DeepSeek 多了 18%——同样的任务,Kimi 的回复更"啰嗦",这多出来的 Token 提升了成本,也延长了生成时间(更多 Token = 更长的流式输出)。

如果网关不做 Token 计量和统计,你永远不知道同样的任务在不同模型上产生的成本差异。聚合网关上的一道 Token 计数聚合,能让你按模型、按时间段、按任务类型拆解消耗——这是单独对接各模型 API 时做不到的全局视角。

但计费有个容易踩的坑。如果按直觉在请求进来的 middleware 里读request.body()统计 Token,你拿到的只是 prompt tokens(输入的 Token 数),而 completion tokens(模型输出的 Token 数)——通常才是计费的大头——根本拿不到。正确做法是在流式响应完全返回后,从响应的usage字段里提取两个值。这里用 FastAPI 的BackgroundTasks,在响应发回给用户之后再异步统计,不阻塞主链路:

fromfastapiimportBackgroundTasks,Requestfromstarlette.responsesimportStreamingResponseimportjsonasyncdefbilling_task(model:str,prompt_tokens:int,completion_tokens:int):"""响应返回给用户之后异步执行,不增加请求延迟"""cost=calculate_cost(model,prompt_tokens,completion_tokens)awaitdb.insert_billing_record(model,prompt_tokens,completion_tokens,cost)metrics.token_total.labels(model=model,type="prompt").inc(prompt_tokens)metrics.token_total.labels(model=model,type="completion").inc(completion_tokens)@app.post("/v1/chat/completions")asyncdefchat_completions(request:Request,background_tasks:BackgroundTasks):body=awaitrequest.json()model=body.get("model","default")# 流式调用下游模型response=awaitproxy_to_model(body)# 等待流式响应完整接收后,提取 usage(此时 prompt + completion 都有)usage=response.get("usage",{})prompt_tokens=usage.get("prompt_tokens",0)completion_tokens=usage.get("completion_tokens",0)# 注册后台任务:响应已经发给用户了,计费在后台异步完成background_tasks.add_task(billing_task,model,prompt_tokens,completion_tokens)returnStreamingResponse(generate_sse_stream(response),media_type="text/event-stream")

上面这段就是我在器灵上调用时用的计费逻辑,响应返回后异步统计,不拖主链路。实际跑下来,加了 BackgroundTasks 之后请求延迟几乎没有感知变化。


压测结论与工程建议

这次压测的三个核心发现:

  1. 网关的安全水位取决于连接池配置,不是 CPU 或内存。连接池大小与目标 RPS、模型响应时间成线性关系,每下游 20-40 连接是黄金区间。

  2. 灰度路由比固定分配对 P99 的改善更显著。按实时延迟加权把流量从慢模型上撤走,P99 可降低 30-40%。

  3. 延迟和 Token 成本不是正相关。最快的模型不一定最省 Token,网关层必须同时监控延迟和 Token 消耗,才能做出正确的路由和选型决策。

如果你管理的多模型 API 调用量达到每天百万级,上述任何一个指标的轻微劣化,都会被放大成可观的成本或用户体验损失。在网关上统一做延迟监控、连接池管理、Token 计费聚合,比每个服务各自维护一套指标系统高效得多。

统一聚合网关的核心价值,其实不在"多一个模型选项",而在把性能可观测性和成本控制从下游模型的黑盒里拉出来,变成网关层可控的变量——延迟能看到、费用能拆开、流量能按实时状态调度。对于有规模要求的工程团队,这才是选聚合方案时最该关注的指标。

http://www.cnnetsun.cn/news/3096670.html

相关文章:

  • 构建高效漏洞速查字典:一句话版本通报的设计与实战
  • 持续沉淀企业人才数据,让 AI 随组织发展不断适配专属管理逻辑
  • Shell脚本精读 · S06-03 | 条件与控制流综合:读 30 行脚本的判断链
  • 【GitHub】图片上传工具PicGo 深度技术解析
  • 【课程设计/毕业设计】基于 SpringBoot 的会议室线上报备与运维系统的设计与实现 基于 SpringBoot 的智能办公场地预约管理系统的设计与实现【附源码、数据库、万字文档】
  • 建站公司怎么推荐才靠谱?从需求清单、报价口径和交付物判断
  • 分布式系统关注点(8)——99%的人都能看懂的「熔断」以及最佳实践
  • 数据库架构演进——从“单间出租“到“合租公寓“
  • 通达信竣宝底部大阳启动量化选股与量化交易指标 大阳不破波浪掘金抓牛股主副图指标 平台突破指标公式
  • 内存是计算机的主存储器。内存为进程开辟出进程空间,让进程在其中保存数据。我将从内存的物理特性出发,深入到内存管理的细节,特别是了解虚拟内存和内存分页的概念。
  • 上门维修电脑的坑,消委会已经发出警示!这几点一定要注意
  • git 将一个本地文件夹初始化成git仓库并且推送到远端git仓库
  • 饲料颗粒机哪家技术强
  • 工程现场施工管理系统怎么选?落地避坑实用指南
  • Java计算机毕设之基于 SpringBoot 的应急物资储备与发放管理系统的设计与实现 基于 SpringBoot 的灾害应急物资供应链管理系统(完整前后端代码+说明文档+LW,调试定制等)
  • 手持Ultra1/S8/SE2的用户
  • WRF模拟全技术链实践暨Linux编译排错、FNL/ERA5驱动场处理、长时序模拟配置、下垫面改造与物理参数调整、Python诊断分析及可视化
  • 工业 AR 眼镜关键技术与主流技术路线分析
  • 深度解密 Linux 保留网段:127、10、172 背后的底层网络内核与现代架构智慧
  • 15-Vue3 性能优化与调试
  • Golang的CSP很酷?其实.NET也可以轻松完成
  • TLSF和伙伴系统融合算法实现
  • 机器学习模型生产化落地:从Notebook到稳定服务的五层加固
  • 基于鲸鱼优化算法(WOA)的路径规划附Matlab代码
  • 锂离子电池过压保护方案与BQ29200应用设计
  • 基于Matlab的车辆ASR驱动防滑转仿真模型(仿真+参考文献)
  • 矩阵正交化处理:提升循环模型噪声关联回忆性能,小改进带来大提升!
  • Java毕设项目: 基于 SpringBoot 的住院患者护理信息管理系统的设计与实现 基于 SpringBoot 的医院病房资源统筹管理系统(源码+文档,讲解、调试运行,定制等)
  • SQL Server数据库同步工具深度对比:6款方案实测与选型(含信创环境选型建议)
  • 亦唐科技在人工智能领域的创新应用与发展