news 2026/10/9 18:26:24

【AIGC】大模型面试高频考点18-大模型压力测试指标:用TaoToken统一Key实测TTFT/TPOT/TPS

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【AIGC】大模型面试高频考点18-大模型压力测试指标:用TaoToken统一Key实测TTFT/TPOT/TPS

1. 面试官为什么总盯着 TTFT、TPOT、TPS 这三个数

大模型压力测试指标里,TTFT、TPOT、TPS 是最容易被追问、也最容易答糊的三个。它们分别回答三个不同的问题:用户等多久才看到第一个字、字与字之间流得顺不顺、整台服务每秒到底能吐多少 token。面试时如果只背定义,面试官一句“那你压测时怎么采、并发上去之后哪个先崩”就能把你问住。这篇就把这三个指标拆开,用 TaoToken 统一 Key 作为调用入口,写一套本地能跑的压测脚本,把并发梯度、指标采集、结果解读一次讲透。

先说清楚这三个指标到底在测什么。TTFT(Time To First Token)是从你发出请求到收到第一个 token 的时间,它包含排队、预填充(prefill)和首 token 解码的全部开销,直接决定用户对“快不快”的第一印象。TPOT(Time Per Output Token)是相邻两个 token 之间的平均间隔,反映解码阶段的流畅度,TPOT 越大,用户越觉得字是一个一个往外蹦。TPS(Token Per Second)是每秒生成的 token 数,衡量整体吞吐,长文本生成任务里它直接决定任务多久跑完。

这三个指标不是孤立的。TTFT 主要吃 prefill 算力和排队调度,TPOT 主要吃 decode 阶段的显存带宽和 batch 策略,TPS 则是两者的综合结果。压测时并发一上来,通常先看到 TTFT 飙升(请求排队),再看到 TPOT 变大(batch 变大后单请求解码变慢),最后 TPS 增长放缓甚至掉头向下——这就是所谓的吞吐拐点。面试里能把这层因果关系讲出来,比背十个定义都管用。

适合谁看:正在准备大模型相关岗位面试的同学、需要给自家推理服务做容量评估的工程师、以及想搞明白“为什么我的服务一上并发就卡”的开发者。下面所有脚本都可以直接复制运行,你只需要一个能调用的 API 通道。

2. 用 TaoToken 统一 Key 搭压测入口,省掉多模型切换的麻烦

做压测最烦的一件事是:不同模型要走不同厂商的 SDK、不同的鉴权方式、不同的返回格式,脚本写一半全耗在适配上了。我这次用 TaoToken 的统一 Key 和统一 API 通道来解决这个问题——它对外暴露的是 OpenAI 兼容的接口,你换模型只需要改一个 model 字段,压测脚本本身不用动。这对做对比压测特别友好:同一份脚本,把 model 从 A 换成 B,就能横向比 TTFT/TPOT/TPS。

TaoToken 在这里扮演的角色是统一的调用入口,不是替代你的压测工具。压测逻辑、并发控制、指标采集还是你自己写,它负责把请求稳定地送到模型并流式返回 token。因为返回是标准 SSE 流式格式,我们才能在客户端精确记录“第一个 token 到达时间”和“每个 token 到达时间”,这两个时间戳正是算 TTFT 和 TPOT 的原始数据。

接入前你需要准备三样东西,这也是后面所有配置的基础:

  • Base URL:https://taotoken.net/api,所有请求都打这个地址,注意不要带多余的路径后缀。
  • API Key:在控制台创建,形如sk-开头的一串字符,压测时建议单独建一个 Key 方便统计用量。
  • Model ID:你要压测的模型标识,比如对话模型或代码模型的具体名字,填在请求体的model字段里。

获取 Key 的入口在控制台的 API Keys 页面,创建后复制保存,页面只完整显示一次。如果你还没决定压哪个模型,可以先去模型对话页面手动发几条请求,确认通道通、返回正常,再进压测环节。对于要长期跑压测或做 Agent 评测的场景,Coding Plan 会更划算,因为压测本身 token 消耗不小。

这里要提醒一句:压测请控制并发和总请求量,别把公共通道当成无限资源猛打。合理的做法是从低并发起步,逐步加压,观察指标变化即可,目的是拿到数据、理解趋势,不是把服务打挂。

3. 可复制的压测配置:请求模板、并发梯度与采集脚本

这一节是全文的核心,给你一份能直接跑的 Python 压测脚本。它做三件事:按并发梯度发请求、流式接收并记录每个 token 的时间戳、最后算出 TTFT/TPOT/TPS 的均值与分位数。依赖只有openai和numpy,装完就能跑。

先看配置部分。把下面这段存成config.json,路径和字段名保持原样,脚本会直接读它:

{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key填这里", "model": "你的模型ID", "prompt": "请用300字介绍大模型推理中的prefill和decode两个阶段", "max_tokens": 300, "temperature": 0.7, "concurrency_levels": [1, 2, 4, 8, 16], "requests_per_level": 8, "timeout": 60 }

字段说明一下:concurrency_levels是并发梯度,从 1 到 16 逐级加压;requests_per_level是每个并发档位发多少个请求,样本太少分位数没意义,建议至少 8 个;max_tokens控制生成长度,压测时固定它才能公平比较 TPS。temperature设 0.7 是为了让输出长度有自然波动,更接近真实场景。

下面是采集脚本bench.py,核心是用流式接口逐 token 打时间戳:

import json import time import asyncio import numpy as np from openai import AsyncOpenAI with open("config.json", "r", encoding="utf-8") as f: CFG = json.load(f) client = AsyncOpenAI( base_url=CFG["base_url"], api_key=CFG["api_key"], timeout=CFG["timeout"], ) async def one_request(): t_send = time.perf_counter() t_first = None t_last = None token_times = [] n_tokens = 0 try: stream = await client.chat.completions.create( model=CFG["model"], messages=[{"role": "user", "content": CFG["prompt"]}], max_tokens=CFG["max_tokens"], temperature=CFG["temperature"], stream=True, ) async for chunk in stream: delta = chunk.choices[0].delta if delta and delta.content: now = time.perf_counter() if t_first is None: t_first = now token_times.append(now) t_last = now n_tokens += 1 except Exception as e: return {"ok": False, "err": str(e)} if t_first is None or n_tokens < 2: return {"ok": False, "err": "no token received"} ttft = (t_first - t_send) * 1000 gaps = np.diff(token_times) * 1000 tpot = float(np.mean(gaps)) tps = (n_tokens - 1) / (t_last - t_first) total = (t_last - t_send) * 1000 return { "ok": True, "ttft": ttft, "tpot": tpot, "tps": tps, "total": total, "n_tokens": n_tokens, } async def run_level(concurrency, n_req): sem = asyncio.Semaphore(concurrency) async def worker(): async with sem: return await one_request() tasks = [worker() for _ in range(n_req)] return await asyncio.gather(*tasks) def summarize(results): ok = [r for r in results if r["ok"]] if not ok: return None ttft = [r["ttft"] for r in ok] tpot = [r["tpot"] for r in ok] tps = [r["tps"] for r in ok] return { "n_ok": len(ok), "n_fail": len(results) - len(ok), "ttft_p50": float(np.percentile(ttft, 50)), "ttft_p95": float(np.percentile(ttft, 95)), "tpot_p50": float(np.percentile(tpot, 50)), "tpot_p95": float(np.percentile(tpot, 95)), "tps_mean": float(np.mean(tps)), "tps_sum": float(np.sum(tps)), } async def main(): for c in CFG["concurrency_levels"]: results = await run_level(c, CFG["requests_per_level"]) s = summarize(results) print(f"并发={c} -> {json.dumps(s, ensure_ascii=False)}") if __name__ == "__main__": asyncio.run(main())

跑之前先pip install openai numpy,然后python bench.py。脚本会按并发 1、2、4、8、16 逐级打印结果。注意tps_sum这一列,它是该并发档位下所有请求 TPS 之和,代表集群总吞吐,和单请求 TPS 要分开看——面试里区分这两个概念是加分项。

关于并发梯度的设置逻辑:从 1 开始是为了拿到“无竞争”的基线值,后面每档翻倍是为了快速找到拐点。如果你压的是长上下文模型,可以把max_tokens调大、并发档位调小,因为长序列的 prefill 开销会主导 TTFT。反过来压短问答场景,就把max_tokens压到 50 以内,重点看 TTFT 和 QPS。

4. 验证请求与成功结果:一次真实压测的数据长什么样

脚本跑通后,你会看到类似下面这样的输出(数值是示例,实际以你的通道和模型为准):

并发=1 -> {"n_ok": 8, "n_fail": 0, "ttft_p50": 420.5, "ttft_p95": 510.2, "tpot_p50": 18.3, "tpot_p95": 22.1, "tps_mean": 54.6, "tps_sum": 436.8} 并发=2 -> {"n_ok": 8, "n_fail": 0, "ttft_p50": 455.1, "ttft_p95": 620.7, "tpot_p50": 19.8, "tpot_p95": 25.4, "tps_mean": 50.5, "tps_sum": 808.0} 并发=4 -> {"n_ok": 8, "n_fail": 0, "ttft_p50": 530.9, "ttft_p95": 810.3, "tpot_p50": 23.6, "tpot_p95": 31.2, "tps_mean": 42.4, "tps_sum": 1356.8} 并发=8 -> {"n_ok": 8, "n_fail": 0, "ttft_p50": 720.4, "ttft_p95": 1250.6, "tpot_p50": 31.5, "tpot_p95": 45.8, "tps_mean": 31.7, "tps_sum": 2028.8} 并发=16 -> {"n_ok": 7, "n_fail": 1, "ttft_p50": 1180.2, "ttft_p95": 2400.9, "tpot_p50": 48.9, "tpot_p95": 72.3, "tps_mean": 20.4, "tps_sum": 2284.5}

先看怎么验证请求是成功的。n_ok等于requests_per_level说明全部成功,n_fail出现非零就要去查错误。上面并发 16 时挂了 1 个,可能是超时或限流,这时候要结合错误信息判断,而不是直接下结论说服务不行。

再读数据。并发从 1 到 8,tps_sum从 436 涨到 2028,说明服务还在扩容,吞吐随并发线性增长,这是健康区间。但tps_mean(单请求 TPS)从 54.6 掉到 31.7,说明每个请求变慢了——这就是 batch 变大后 decode 被摊薄的代价。到并发 16,tps_sum只从 2028 涨到 2284,增幅明显放缓,同时ttft_p95冲到 2400ms、tpot_p95到 72ms,还出现了失败请求。这个点就是吞吐拐点:再加并发,吞吐涨不动,延迟和错误率却继续恶化。

面试里怎么讲这组数据?你可以说:TTFT 的 p95 比 p50 涨得快,说明排队是长尾的主要来源;TPOT 随并发单调上升,说明 decode 阶段是瓶颈;TPS 总和在并发 8 之后接近饱和,说明当前配置下的最优并发在 8 附近。调优方向也就出来了——要么加推理实例横向扩容,要么优化 batch 调度策略,要么对长请求做限流保护。

如果你只想快速验证通道和指标采集逻辑对不对,可以先跑并发 1 的单档,确认n_ok全绿、TTFT 在几百毫秒量级、TPOT 稳定,再放开梯度。手动验证时也可以去模型对话页面发一条同样的 prompt,肉眼感受一下首字延迟和出字速度,和脚本数据对得上就说明采集没问题。

5. 压测常见报错排查:401、超时、空 choices 怎么定位

压测脚本报错和普通调用报错不太一样,因为并发一上来,很多问题是并发触发的。下面按真实会遇到的报错逐个说。

401 Unauthorized / invalid api key:最常见。先检查config.json里的api_key有没有多余空格、有没有把 Key 复制漏字符。TaoToken 的 Key 是sk-开头,如果你用的是环境变量注入,确认变量名没写错。还有一种情况是 Key 被禁用或额度耗尽,去控制台 API Keys 页面看一眼状态。注意 Base URL 必须是https://taotoken.net/api,写成别的路径也可能导致鉴权失败。

Connection timeout / Read timed out:并发高时请求排队,超过timeout设置就会抛这个。先把timeout从 60 调到 120 试试,如果还是超时,说明该并发档位已经超过服务承载能力,这本身就是有价值的压测结论,不用强行压过去。排查时可以把并发降回上一档,确认是并发问题还是通道问题。

返回里 choices 为空 / no token received:流式接口偶尔会返回只有 role 没有 content 的 chunk,脚本里已经用if delta and delta.content过滤了。如果整条请求一个 content 都没有,可能是max_tokens设得太小被截断,或者 prompt 触发了内容策略。把max_tokens调到 100 以上再试,同时换一条中性 prompt 排除输入问题。

local proxy failed / 代理相关报错:如果你本地配了网络代理,OpenAI SDK 可能会走代理导致连接异常。压测时建议清掉HTTP_PROXY、HTTPS_PROXY环境变量,让请求直连。这个报错和通道本身无关,是本地网络配置问题。

OAuth / 鉴权方式不匹配:有些工具默认走 OAuth 或别的鉴权流程,而 TaoToken 用的是 API Key 方式。如果你在 Cline、CC Switch 这类工具里配置,记得选 API Key 模式,Base URL 填https://taotoken.net/api,Model ID 填你要用的模型。这三件套(Base URL + Key + Model ID)缺一不可,配错任何一个都会报鉴权或模型不存在。

模型不存在 / model not found:model字段拼错了,或者你填的模型 ID 当前通道不支持。去文档页核对可用的 Model ID 列表,复制粘贴而不是手打。

排查顺序建议固定成:先看 HTTP 状态码(401 查 Key、404 查模型、429 查限流、5xx 查服务端),再看是单请求失败还是并发下才失败,最后看是通道问题还是本地网络问题。把这套顺序讲出来,面试官会觉得你是真压过。

6. 把指标讲成故事:面试与实战里的调优方向

压测的终点不是一堆数字,而是能回答“这个服务能扛多少并发、瓶颈在哪、怎么优化”。TTFT 高,优先查 prefill 和排队,方向是加实例、优化调度、缩短输入;TPOT 高,优先查 decode 阶段的显存带宽和 batch 大小,方向是调 batch 策略、用量化、换更快的解码 kernel;TPS 上不去,先看是单请求慢还是总吞吐饱和,前者优化单请求,后者横向扩容。

面试里被问到“你怎么定容量”,可以这样答:先用低并发拿基线,再按梯度加压找到 TPS 拐点,取拐点前 70% 的并发作为安全水位,同时保证 p95 TTFT 在可接受范围内。这套方法不依赖具体模型,换任何服务都能用。

最后给个实操建议:把每次压测的config.json和输出结果按时间存档,模型版本、通道、并发梯度都记下来。下次服务变慢时,翻出历史数据一对比,是模型换了、通道抖了还是并发涨了,一目了然。压测这件事,数据积累比单次结果值钱得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 18:26:01

电子报纸订购系统数据库课设说明书写作指南

简介&#xff1a;本资源是一份完整的数据库课程设计实践文档&#xff0c;面向高校计算机及相关专业本科生&#xff0c;解决数据库课设中电子报纸订购系统从需求分析到系统实现的全流程方案落地问题。文档以Word格式&#xff08;.doc&#xff09;单文件封装&#xff0c;大小5.94…

作者头像 李华
网站建设 2026/10/9 18:19:38

移动APN接入点怎么选?实测网速翻倍的配置与避坑指南

1. 移动APN接入点到底是什么&#xff0c;为什么它会影响网速很多人第一次听到“APN”这个词&#xff0c;是在换手机卡、刷机或者手动配置网络参数的时候。APN的全称是Access Point Name&#xff0c;中文叫“接入点名称”。你可以把它理解成手机连接运营商移动网络时的一张“通行…

作者头像 李华
网站建设 2026/10/9 18:17:09

AVEVA项目管理核心:三层数据契约与工程交付闭环

简介&#xff1a;本资源是一份面向工业自动化工程师、系统集成人员及项目管理从业者的AVEVA系统平台专项教程&#xff0c;聚焦项目全生命周期管理实践&#xff0c;帮助用户掌握工程设计、数据集成、进度跟踪与运营优化等核心能力。文档为单文件Word格式&#xff08;.docx&#…

作者头像 李华
网站建设 2026/10/9 18:14:39

PCA9422与PIC18F4610组合的便携设备电源管理实战

做电池供电的便携设备&#xff0c;最烦人的往往不是业务代码&#xff0c;而是电源那一摊子事。以前我做一个小型手持项目&#xff0c;光电源部分就用了三颗LDO、一颗升压、一颗充电管理芯片&#xff0c;再加上一堆分立MOS和RC延时电路&#xff0c;PCB上密密麻麻全是电源布线的影…

作者头像 李华
网站建设 2026/10/9 18:14:15

MATLAB谐振腔模拟:ABCD矩阵、稳定分析与模式求解指南

简介&#xff1a;面向激光物理、光学工程专业学习者及科研人员的MATLAB激光器谐振腔模拟分析资源&#xff0c;围绕平行平面腔等典型结构&#xff0c;演示如何通过波动光学方法建立传播模型并迭代求解&#xff0c;帮助理解谐振腔对输出功率与光束质量的影响。压缩包仅9KB&#x…

作者头像 李华