多模型 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 次取分布。
| 模型 | P50 | P95 | P99 | 平均首 Token 延迟 |
|---|---|---|---|---|
| DeepSeek V4 | 1182ms ± 286ms | 2734ms ± 512ms | 3618ms ± 742ms | 423ms ± 87ms |
| Qwen3.6 Max | 1537ms ± 341ms | 3182ms ± 604ms | 4092ms ± 891ms | 512ms ± 103ms |
| Kimi K2.7 | 2074ms ± 518ms | 4516ms ± 903ms | 5783ms ± 1245ms | 689ms ± 156ms |
| GLM-5.2 | 1831ms ± 429ms | 3922ms ± 751ms | 5108ms ± 1067ms | 574ms ± 132ms |
三个观察:
- DeepSeek 在短 Prompt 下最稳定,P50→P99 的劣化幅度最小(1182ms→3618ms,约 3 倍),标准差也控制得最好,适合对延迟敏感的实时场景。
- Kimi 的长尾最严重,P99 达到 5783ms,标准差 1245ms 远超其他模型,说明偶发性的服务端排队或 GC 延迟波动很大。
- 首 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| RPS | P50 | P95 | P99 | 错误率 | 网关 CPU |
|---|---|---|---|---|---|
| 10 | 1406ms ± 312ms | 3074ms ± 618ms | 4012ms ± 852ms | 0% | 8% |
| 50 | 1638ms ± 407ms | 3817ms ± 743ms | 5224ms ± 1061ms | 0% | 22% |
| 100 | 1921ms ± 493ms | 5083ms ± 1127ms | 7816ms ± 1834ms | 0.2% | 45% |
| 200 | 3184ms ± 1026ms | 11472ms ± 3802ms | 18253ms ± 6218ms | 2.1% | 78% |
关键转折点在100 RPS → 200 RPS:P99 从 7816ms 飙升到 18253ms,错误率从 0.2% 跳到 2.1%。翻看网关日志,这个阶段的下游连接池开始出现排队——请求在网关内部等可用连接的时间超过了实际的模型推理时间。
结论:以当前配置(4 个下游,每下游 20 连接池),网关的安全水位在100 RPS 左右。超过这个量,瓶颈不在网关的处理性能,在连接池的排队深度。
连接池:被低估的延迟放大器
HTTP 连接池的配置直接影响尾延迟。两个极端:
- 池太小:请求排队等连接,延迟飙升。
- 池太大:下游模型被超额连接打满,触发限流或 OOM,反而更慢。
做一个对照实验:固定在 100 RPS,变化下游连接池大小(per downstream),看延迟和错误率的变化。
| 每下游连接数 | P50 | P99 | 网关排队占比 | 下游限流次数 |
|---|---|---|---|---|
| 5 | 5823ms ± 1406ms | 14208ms ± 3817ms | 72% | 0 |
| 10 | 2405ms ± 592ms | 9134ms ± 2602ms | 38% | 0 |
| 20 | 1921ms ± 493ms | 7816ms ± 1834ms | 15% | 0 |
| 40 | 1847ms ± 458ms | 8472ms ± 2051ms | 6% | 12 |
| 80 | 2106ms ± 713ms | 12263ms ± 3948ms | 3% | 47 |
最优区间在20-40 连接/下游。40 连接时 P99 反而略高于 20 连接——下游模型开始触发频率限制,部分请求被 429 拦截,重试拉高了尾延迟。到 80 连接时限制次数飙升,得不偿失。
实用规则:连接池大小 ≈ 目标 RPS × 模型平均响应时间 ÷ 下游实例数 × 1.5(留 50% 余量)。对本场景:100 × 1.92s ÷ 1 × 1.5 ≈ 29 连接,落在实测的最优区间内。
跨模型延迟方差:路由策略影响有多大
前面是均匀分配流量。如果把 100 RPS 全部路由到最快的 DeepSeek 和最慢的 Kimi,延迟分布差多少?
| 路由策略 | 目标模型 | P50 | P99 | 100 RPS 下的错误率 |
|---|---|---|---|---|
| 全部 DeepSeek | DeepSeek V4 | 1317ms ± 304ms | 4235ms ± 961ms | 0% |
| 全部 Kimi | Kimi K2.7 | 2486ms ± 627ms | 7129ms ± 1804ms | 0.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 V4 | 142 | 215 | ¥0.0032 | 4235ms ± 961ms |
| Qwen3.6 Max | 138 | 215 | ¥0.0048 | 4812ms ± 1073ms |
| Kimi K2.7 | 168 | 220 | ¥0.0061 | 7129ms ± 1804ms |
| GLM-5.2 | 145 | 218 | ¥0.0052 | 5926ms ± 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 之后请求延迟几乎没有感知变化。
压测结论与工程建议
这次压测的三个核心发现:
网关的安全水位取决于连接池配置,不是 CPU 或内存。连接池大小与目标 RPS、模型响应时间成线性关系,每下游 20-40 连接是黄金区间。
灰度路由比固定分配对 P99 的改善更显著。按实时延迟加权把流量从慢模型上撤走,P99 可降低 30-40%。
延迟和 Token 成本不是正相关。最快的模型不一定最省 Token,网关层必须同时监控延迟和 Token 消耗,才能做出正确的路由和选型决策。
如果你管理的多模型 API 调用量达到每天百万级,上述任何一个指标的轻微劣化,都会被放大成可观的成本或用户体验损失。在网关上统一做延迟监控、连接池管理、Token 计费聚合,比每个服务各自维护一套指标系统高效得多。
统一聚合网关的核心价值,其实不在"多一个模型选项",而在把性能可观测性和成本控制从下游模型的黑盒里拉出来,变成网关层可控的变量——延迟能看到、费用能拆开、流量能按实时状态调度。对于有规模要求的工程团队,这才是选聚合方案时最该关注的指标。
