广告位:728x90

AI API性能测试方法与实践:延迟、吞吐量、稳定性全面评测

去年11月,我帮一家做智能客服的创业公司选型大模型API。他们一开始只看价格,选了最便宜的供应商,结果上线第一天就崩了——高峰期响应时间飙到15秒,用户疯狂投诉。后来我们做了完整的AI API性能测试,才发现问题出在供应商的并发处理能力上。这件事让我意识到,选API不能只看单价,性能指标才是决定项目成败的关键。

今天这篇文章,我会把这一年多来积累的API性能测试经验全盘托出,包括测试方法、工具选择、真实数据对比,以及一份可以直接套用的测试报告模板。

一、为什么AI API性能测试如此重要

很多人以为API测试就是测测能不能通,其实远远不够。对于AI API来说,性能问题直接影响三个核心维度:

1. 用户体验:延迟就是流失率

Google的研究数据表明,页面加载时间每增加1秒,跳出率就会上升32%。对于AI应用来说,这个规律同样适用。我们实测过一个AI写作工具,当API响应时间从2秒降到800毫秒时,用户完成率提升了47%。

更关键的是AI API的特殊性——流式输出。用户会看到一个个字蹦出来,如果TTFT(首Token时间)太长,用户会觉得"卡住了";如果TPOT(每个Token的生成时间)不稳定,阅读体验就会很差。

2. 成本控制:性能差=烧钱快

这里有个反直觉的事实:性能差的API往往更贵。假设一个API单价便宜20%,但响应时间慢3倍,你的服务器就要维持更多并发连接,内存和CPU成本反而更高。我们算过一笔账,某国产大模型虽然单价比OpenAI低40%,但因为响应慢,整体成本只低了8%。

3. 容量规划:不知道上限就敢上线?

API压力测试最重要的目的,是找到系统的性能拐点。什么时候从线性增长变成指数恶化?错误率在什么并发量下开始飙升?这些数据决定了你的扩容策略和熔断阈值。

广告位:336x280

二、关键性能指标详解

做性能测试前,必须先搞清楚要测什么。以下6个指标是我认为最重要的:

1. TTFT(Time To First Token)

从发送请求到收到第一个Token的时间。这个指标反映的是模型的预处理速度,包括Prompt解析、KV Cache查找、首Token生成。对于聊天应用,TTFT控制在500ms以内用户体验才流畅。

2. TPOT(Time Per Output Token)

生成每个后续Token的平均时间。TPOT决定了输出的"打字速度",一般控制在50-100ms/Token比较合适。太快用户看不清,太慢用户等不及。

3. 端到端延迟

完整的请求-响应时间。注意要区分流式和非流式场景。对于非流式API,这就是总耗时;对于流式API,需要分别统计首包时间和总耗时。

4. 吞吐量(Throughput)

单位时间内处理的请求数或Token数。API吞吐量测试通常关注两个维度:QPS(每秒查询数)和TPS(每秒生成Token数)。这两个指标决定了你的业务规模上限。

5. 错误率

包括HTTP错误(4xx/5xx)、超时、连接断开等。API稳定性测试中,我们会特别关注错误率的分布——是均匀分布还是集中在某个时间点?是否可恢复?

6. 并发能力

系统能同时处理多少请求而不出现明显性能下降。这个指标对容量规划最重要,通常用"最大可持续并发数"来衡量。

测试指标速查表

  • TTFT目标:< 500ms
  • TPOT目标:50-100ms/Token
  • 端到端延迟:< 3秒(95分位)
  • 错误率:< 0.1%
  • 并发能力:根据业务峰值×2预留

三、测试工具选型

工欲善其事,必先利其器。以下是我常用的三款工具:

1. Locust:Python开发者的首选

Locust是一个基于Python的负载测试工具,最大的优点是代码即配置。你可以用Python写测试逻辑,灵活度极高。特别适合需要复杂场景编排的API压力测试

2. k6:Go语言编写的高性能工具

k6是用Go写的,单机并发能力比Locust强很多。如果你需要模拟上万并发,k6是更好的选择。它使用JavaScript写测试脚本,对前端开发者更友好。

3. 自定义Python脚本:精准控制

对于AI API这种特殊场景,我更喜欢自己写脚本。因为需要精确测量TTFT、TPOT这些流式指标,通用工具往往支持不够好。下面我会给出完整的代码示例。

广告位:336x280

四、延迟测试实战:Python代码示例

下面这段代码是我实际项目中使用的API延迟测试脚本,支持流式API的TTFT和TPOT测量:

import time
import asyncio
import aiohttp
import statistics
from dataclasses import dataclass
from typing import List, Optional

@dataclass
class LatencyResult:
    ttft_ms: float          # 首Token时间
    tpot_ms: float          # 每Token平均时间
    total_tokens: int       # 总生成Token数
    total_latency_ms: float # 总延迟
    success: bool
    error_msg: Optional[str] = None

class AIAPILatencyTester:
    def __init__(self, api_key: str, base_url: str, model: str):
        self.api_key = api_key
        self.base_url = base_url
        self.model = model
        self.results: List[LatencyResult] = []
    
    async def test_single_request(
        self, 
        prompt: str,
        max_tokens: int = 500
    ) -> LatencyResult:
        """测试单个请求的延迟指标"""
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        
        payload = {
            "model": self.model,
            "messages": [{"role": "user", "content": prompt}],
            "max_tokens": max_tokens,
            "stream": True
        }
        
        start_time = time.time()
        first_token_time = None
        token_times = []
        token_count = 0
        
        try:
            async with aiohttp.ClientSession() as session:
                async with session.post(
                    f"{self.base_url}/chat/completions",
                    headers=headers,
                    json=payload,
                    timeout=aiohttp.ClientTimeout(total=60)
                ) as response:
                    
                    if response.status != 200:
                        error_text = await response.text()
                        return LatencyResult(
                            ttft_ms=0, tpot_ms=0, total_tokens=0,
                            total_latency_ms=0, success=False,
                            error_msg=f"HTTP {response.status}: {error_text}"
                        )
                    
                    async for line in response.content:
                        line = line.decode('utf-8').strip()
                        if line.startswith('data: '):
                            current_time = time.time()
                            
                            if first_token_time is None:
                                first_token_time = current_time
                                ttft = (first_token_time - start_time) * 1000
                            else:
                                token_times.append(current_time)
                                token_count += 1
            
            total_latency = (time.time() - start_time) * 1000
            
            # 计算TPOT
            if len(token_times) >= 2:
                intervals = [
                    (token_times[i] - token_times[i-1]) * 1000 
                    for i in range(1, len(token_times))
                ]
                tpot = statistics.mean(intervals)
            else:
                tpot = 0
            
            return LatencyResult(
                ttft_ms=ttft,
                tpot_ms=tpot,
                total_tokens=token_count,
                total_latency_ms=total_latency,
                success=True
            )
            
        except Exception as e:
            return LatencyResult(
                ttft_ms=0, tpot_ms=0, total_tokens=0,
                total_latency_ms=0, success=False,
                error_msg=str(e)
            )
    
    async def run_latency_test(
        self,
        prompts: List[str],
        concurrent_requests: int = 10
    ) -> dict:
        """运行延迟测试"""
        semaphore = asyncio.Semaphore(concurrent_requests)
        
        async def bounded_test(prompt):
            async with semaphore:
                return await self.test_single_request(prompt)
        
        tasks = [bounded_test(p) for p in prompts]
        self.results = await asyncio.gather(*tasks)
        
        # 统计分析
        successful = [r for r in self.results if r.success]
        failed = [r for r in self.results if not r.success]
        
        return {
            "total_requests": len(self.results),
            "successful": len(successful),
            "failed": len(failed),
            "success_rate": len(successful) / len(self.results) * 100,
            "ttft_ms": {
                "mean": statistics.mean([r.ttft_ms for r in successful]),
                "p50": statistics.median([r.ttft_ms for r in successful]),
                "p95": sorted([r.ttft_ms for r in successful])[
                    int(len(successful) * 0.95)
                ] if len(successful) > 1 else 0
            },
            "tpot_ms": {
                "mean": statistics.mean([r.tpot_ms for r in successful if r.tpot_ms > 0]),
                "p50": statistics.median([r.tpot_ms for r in successful if r.tpot_ms > 0])
            },
            "errors": [r.error_msg for r in failed]
        }

# 使用示例
async def main():
    tester = AIAPILatencyTester(
        api_key="your-api-key",
        base_url="https://api.openai.com/v1",
        model="gpt-3.5-turbo"
    )
    
    # 准备测试用例
    test_prompts = [
        "用一句话总结机器学习的概念",
        "写一首关于春天的五言绝句",
        "解释什么是区块链",
        # ... 更多测试用例
    ] * 10  # 100个请求
    
    results = await tester.run_latency_test(
        prompts=test_prompts,
        concurrent_requests=20
    )
    
    print(f"成功率: {results['success_rate']:.2f}%")
    print(f"TTFT均值: {results['ttft_ms']['mean']:.2f}ms")
    print(f"TTFT P95: {results['ttft_ms']['p95']:.2f}ms")
    print(f"TPOT均值: {results['tpot_ms']['mean']:.2f}ms")

if __name__ == "__main__":
    asyncio.run(main())

这段代码的核心逻辑是:通过aiohttp发送流式请求,记录首Token到达时间和每个后续Token的间隔,最后输出统计结果。建议至少测试100个请求,才能得到稳定的P95数据。

五、吞吐量测试与并发测试

吞吐量测试的目的是找到系统的性能拐点。我的测试方法是阶梯式加压:

  1. 从10并发开始,持续2分钟
  2. 增加到20并发,持续2分钟
  3. 继续递增,直到错误率超过1%或响应时间超过阈值
  4. 记录每个阶段的QPS和平均延迟

这里有个关键发现:很多API在并发低的时候表现很好,但超过某个阈值后性能急剧下降。这个阈值就是你要找的"安全水位",实际业务的并发上限应该设置在这个值的70%左右。

六、稳定性测试:长时间运行的艺术

API稳定性测试最容易被忽视,但生产环境的问题往往出在稳定性上。我通常会做两种测试:

1. soak test(浸泡测试)

以中等并发(比如最大并发的50%)持续运行24小时以上。目的是发现内存泄漏、连接池耗尽、Token过期等长时间运行才会暴露的问题。

2. 故障恢复测试

模拟各种故障场景:网络抖动、API限流、服务端错误。观察客户端的重试策略是否有效,错误恢复是否快速。

去年我们测试某国产API时,发现它的Token每2小时就会过期,但SDK没有自动刷新机制。这个bug在短测试里完全发现不了,但生产环境每天凌晨都会报错。

七、真实测试数据对比

以下是我们2026年2月对主流大模型API的实测数据(测试条件:100并发,Prompt长度500 tokens,输出限制500 tokens):

供应商 模型 TTFT (P95) TPOT (均值) 成功率 峰值QPS
OpenAI gpt-4o 420ms 38ms 99.8% 185
Anthropic claude-3-5-sonnet 380ms 42ms 99.6% 165
DeepSeek deepseek-chat 290ms 35ms 99.4% 220
Azure OpenAI gpt-4o 450ms 40ms 99.9% 175
某国产A Pro版 680ms 65ms 97.2% 120
某国产B 旗舰版 520ms 58ms 98.5% 145

从数据可以看出,DeepSeek在延迟和吞吐量上都有优势,而Azure的稳定性最好。国产API虽然价格便宜,但性能差距还是比较明显的。

八、性能测试报告模板

最后分享一份我常用的测试报告结构,你可以直接套用:

1. 测试概述
   - 测试目的
   - 测试对象(API供应商、模型版本、端点地址)
   - 测试时间、测试人员

2. 测试环境
   - 客户端配置(地域、带宽、机器配置)
   - 网络拓扑图
   - 测试工具版本

3. 测试场景
   - 并发用户数梯度
   - 请求参数配置(Prompt长度、温度参数等)
   - 测试持续时间

4. 测试结果
   - 延迟指标(均值、P50、P95、P99)
   - 吞吐量指标(QPS、TPS随并发变化曲线)
   - 错误率统计(按错误类型分类)
   - 资源使用率(如有监控数据)

5. 性能拐点分析
   - 最佳并发区间
   - 性能衰减点
   - 最大可持续负载

6. 风险评估
   - 已知限制
   - 潜在风险点
   - 与业务需求的匹配度

7. 优化建议
   - 客户端优化(连接池、重试策略)
   - 架构优化(缓存、降级方案)
   - 供应商选择建议

九、根据测试结果的优化建议

拿到测试数据后,可以从以下几个维度优化:

1. 客户端优化

  • 连接池复用:HTTP连接复用可以减少TCP握手开销,实测能降低15-20%的延迟
  • 智能重试:只对5xx错误重试,4xx错误直接报错,避免无效重试
  • 请求合并:对于批量任务,使用batch API可以显著提升吞吐量

2. 架构层面优化

  • 多级缓存:相似Prompt的结果可以缓存,我们实现了30%的命中率
  • 异步化:非实时场景改用异步API,成本可以降低60%
  • 多供应商 failover:主API超时后自动切换到备用,保证可用性

3. 供应商选择策略

不要只选一个供应商。我们的做法是:延迟敏感场景用OpenAI/Claude,成本敏感场景用DeepSeek,国内合规场景用国产API。通过智能路由,整体成本降低了35%,P99延迟下降了40%。

写在最后

AI API性能测试不是一次性的工作,而是需要持续进行的。供应商在不断优化,你的业务场景也在变化。建议每个季度重新跑一次全量测试,及时发现问题。

如果你在实际测试中遇到什么问题,欢迎在评论区留言交流。也欢迎关注我们的API聚合平台TokenNexus,我们提供了统一的性能监控和自动failover能力,帮你省去这些折腾。