1. 推理基准测试到底在测什么,为什么你需要统一 Key 跑评测
大模型推理基准测试,说白了就是给模型或推理服务出一套标准考卷,用同一批题目、同一套打分规则,量出它在真实业务里的表现。它和普通的功能测试不一样:功能测试只关心答案对不对,而推理基准测试要同时盯住准确率、首 Token 时间、Token 间延迟、吞吐量、Token 消耗这几条线。因为线上成本往往不是被“答错”拖垮的,而是被“答得慢、答得贵”拖垮的。
我见过不少团队的做法是:拿几个 prompt 手动在网页里点一点,感觉“挺快的”,就上线了。结果一到晚高峰,用户排队等首字,账单还翻倍。问题就出在没有可复现的评测链路。推理基准测试要解决的,正是把“感觉快”变成“TTFT 320ms、ITL 18ms、TPS 42、单请求消耗 780 Token”这种能对比、能回归的数字。
适合读这篇的人有三类:一是正在选模型或推理服务的工程同学,需要横向对比;二是做 Agent、RAG 应用,想压延迟和成本的开发者;三是要把评测接进 CI、做版本回归的团队。这三类人有个共同痛点——评测脚本要同时打多个模型通道,每个通道一套 Key、一套 Base URL,改起来烦,还容易把 Key 写进代码里。
所以这篇的落地视角是:用 TaoToken 的统一 Key 和统一 API 通道,把“数据集选择 → 批量发起推理请求 → 采集核心指标 → 对比输出”这条链路一次跑通。你只需要维护一份配置,就能把请求打到不同模型上,指标口径也保持一致。下面从接入准备开始,一步步给可复制的配置和脚本。
2. TaoToken 统一 Key 接入准备与评测环境搭建
先说清楚 TaoToken 在这条链路里的角色。它是一个统一的大模型 API 通道,你拿到一个 Key,配一个 Base URL,就能用 OpenAI 兼容的方式调用多家模型。对评测来说,最大的好处是:评测脚本不用为每个模型写一套 SDK 适配,请求体、返回结构、流式解析都统一,指标采集代码只写一遍。
接入前你需要准备三样东西:一个 TaoToken API Key、一个能跑 Python 的环境(建议 3.10+)、以及你要评测的模型 ID 列表。Key 在控制台的 API Keys 页面创建,地址是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。创建后复制保存,它只完整显示一次。
Base URL 用 https://taotoken.net/api ,注意这个地址后面不加任何 UTM 参数,脚本里直接写死即可。模型 ID 建议先去模型对话页面确认一下当前可用的名称,避免拼错:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 。
环境依赖很简单,装三个包就够:
pip install openai httpx pandasopenai用来发请求(TaoToken 兼容 OpenAI 协议),httpx用来做更细的流式计时,pandas用来汇总指标。装完后建议先做一次最小连通性验证,别急着写完整评测脚本。最小验证只需要一个请求,确认 Key、Base URL、模型 ID 三件套都对:
from openai import OpenAI client = OpenAI( api_key="你的_TaoToken_Key", base_url="https://taotoken.net/api" ) resp = client.chat.completions.create( model="你确认过的模型ID", messages=[{"role": "user", "content": "只回复两个字:收到"}], max_tokens=16 ) print(resp.choices[0].message.content)如果这一步返回“收到”,说明通道通了。如果报 401,先别怀疑代码,九成是 Key 复制时带了空格或者用了别的项目的 Key。如果报模型不存在,回到模型对话页面核对 ID 拼写。这一步过了,再进入评测脚本,能省掉大量“到底是网络问题还是代码问题”的排查时间。
环境变量建议这样管理,别把 Key 硬编码进脚本:
export TAOTOKEN_API_KEY="你的_TaoToken_Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"脚本里用os.environ读取。这样评测脚本可以进 Git,Key 不会泄露。长期跑评测或 Agent 任务的话,可以考虑 Coding Plan,额度更稳,适合反复回归:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。
3. 可复制的评测脚本配置:数据集、批量请求与指标采集
这一节是核心,给一份能直接跑的评测脚本骨架。先定评测数据集。推理基准测试常用的数据集有 MMLU(知识)、GSM8K(数学推理)、HumanEval(代码)、以及自建的业务问答集。选数据集的原则是:题目要能自动判分,且输入输出长度分布贴近你的真实场景。比如你做摘要,就别用输出超长的推理题集,否则 ITL 数据没参考价值。
下面这份配置用 JSON 描述评测任务,路径和字段都写清楚,方便你改:
{ "eval_name": "gsm8k_smoke", "base_url": "https://taotoken.net/api", "models": ["模型A_ID", "模型B_ID"], "dataset_path": "./data/gsm8k_sample.jsonl", "concurrency": 4, "max_tokens": 512, "temperature": 0, "ignore_eos": false, "stream": true, "output_path": "./results/eval_result.csv" }几个参数解释一下。concurrency是并发数,建议从 1 开始,逐步加到略高于推理引擎的最大批处理大小,观察 TPS 饱和点。temperature设 0 走 Greedy,评测时最稳,避免随机性干扰。ignore_eos在测固定输出长度时设为 true,让模型生成到max_tokens才停,这样 ITL 和 TPS 才可比。stream必须开,否则拿不到 TTFT。
数据集用 JSONL,每行一道题:
{"id": "q1", "prompt": "小明有12个苹果,给了小红5个,又买了8个,现在有几个?", "answer": "15"} {"id": "q2", "prompt": "一个班40人,60%是女生,女生多少人?", "answer": "24"}批量请求和指标采集脚本如下,重点是流式计时:
import os, json, time, csv from concurrent.futures import ThreadPoolExecutor from openai import OpenAI API_KEY = os.environ["TAOTOKEN_API_KEY"] BASE_URL = os.environ["TAOTOKEN_BASE_URL"] def load_dataset(path): with open(path, "r", encoding="utf-8") as f: return [json.loads(line) for line in f if line.strip()] def run_one(client, model, item, cfg): start = time.perf_counter() ttft = None token_times = [] text = "" stream = client.chat.completions.create( model=model, messages=[{"role": "user", "content": item["prompt"]}], max_tokens=cfg["max_tokens"], temperature=cfg["temperature"], stream=True ) for chunk in stream: delta = chunk.choices[0].delta.content if delta: now = time.perf_counter() if ttft is None: ttft = now - start token_times.append(now) text += delta end = time.perf_counter() e2e = end - start n_tokens = len(token_times) itl = None if n_tokens > 1: itl = (token_times[-1] - token_times[0]) / (n_tokens - 1) tps = n_tokens / e2e if e2e > 0 else 0 return { "id": item["id"], "model": model, "ttft_ms": round(ttft * 1000, 2) if ttft else None, "itl_ms": round(itl * 1000, 2) if itl else None, "e2e_ms": round(e2e * 1000, 2), "tokens": n_tokens, "tps": round(tps, 2), "output": text.strip(), "answer": item.get("answer", "") } def run_eval(cfg): client = OpenAI(api_key=API_KEY, base_url=BASE_URL) data = load_dataset(cfg["dataset_path"]) rows = [] for model in cfg["models"]: with ThreadPoolExecutor(max_workers=cfg["concurrency"]) as pool: futures = [pool.submit(run_one, client, model, item, cfg) for item in data] for fut in futures: rows.append(fut.result()) with open(cfg["output_path"], "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=list(rows[0].keys())) writer.writeheader() writer.writerows(rows) return rows if __name__ == "__main__": with open("./config/eval_config.json", "r", encoding="utf-8") as f: cfg = json.load(f) run_eval(cfg)这份脚本把 TTFT、ITL、e2e、TPS、Token 数都采下来了。准确率单独算:把output和answer做字符串匹配或正则抽取数字后比对。注意 ITL 的计算排除了首 Token,和 GenAI-Perf 的口径一致,这样跨工具对比不会打架。
4. 验证请求与成功结果:跑通一次完整评测
配置写好后,先别上全量数据集,用 5 条样本做冒烟测试。把dataset_path指向gsm8k_sample.jsonl,concurrency设 1,models只放一个模型。运行:
python eval_runner.py成功的话,./results/eval_result.csv会生成,内容类似:
| id | model | ttft_ms | itl_ms | e2e_ms | tokens | tps |
|---|---|---|---|---|---|---|
| q1 | 模型A | 412.35 | 19.82 | 1580.44 | 58 | 36.70 |
| q2 | 模型A | 388.10 | 18.45 | 1320.77 | 49 | 37.10 |
看到这张表,说明链路通了。接下来做三件事验证结果可信度。第一,把concurrency从 1 调到 4、8,观察 TPS 是否先升后饱和、TTFT 是否随并发上升。如果 TPS 一直线性涨,说明还没压到瓶颈;如果 TTFT 暴涨而 TPS 不涨,说明排队严重。第二,换第二个模型 ID 再跑一遍,对比同一批题目的 TTFT 和 ITL,这才是横向评测的意义。第三,把ignore_eos设 true、max_tokens设 256,跑一次固定长度测试,看 ITL 是否稳定——ITL 稳定说明内存管理和带宽利用没问题。
准确率验证用一个小脚本:
import csv, re def extract_num(s): m = re.search(r"-?\d+", s.replace(",", "")) return m.group() if m else None correct = total = 0 with open("./results/eval_result.csv", encoding="utf-8") as f: for row in csv.DictReader(f): total += 1 if extract_num(row["output"]) == row["answer"]: correct += 1 print(f"准确率: {correct}/{total} = {correct/total:.2%}")冒烟测试准确率不用太在意,样本太少。它的作用是确认判分逻辑没写错。真正评测时数据集至少几百条,准确率才有统计意义。到这里,你已经有了可复现的评测结果,换模型、换并发、换数据集都只是改配置的事。
5. 本篇常见报错排查:401、local proxy failed、reading choices、OAuth
评测跑不起来,报错基本集中在这几类,逐个说清楚。
401 Unauthorized。最常见。原因通常是 Key 没读到、Key 失效、或者 Base URL 写错。先确认环境变量:echo $TAOTOKEN_API_KEY有没有值。再确认base_url是https://taotoken.net/api,结尾不要多斜杠、不要拼成别的路径。如果 Key 是从别处复制的,注意首尾空格。还有一种情况是用了已删除的 Key,回控制台重新建一个即可。
local proxy failed / connection error。这类报错说明请求根本没出去,或者被本机网络配置拦了。检查你的运行环境有没有设置HTTP_PROXY、HTTPS_PROXY环境变量,有的话先清掉再跑。另外确认机器能正常访问外网。如果是在容器里跑,检查容器网络是否正常。这类问题和 Key 无关,别反复重建 Key。
reading choices / IndexError: list index out of range。报错出现在解析返回时,chunk.choices[0]取不到。原因一般是:请求被限流返回了错误结构、或者模型返回了空 choices。处理办法是加防御:
for chunk in stream: if not chunk.choices: continue delta = chunk.choices[0].delta.content if delta: ...同时把max_tokens设得太小也可能导致空返回,评测时别低于 64。
OAuth / authentication 相关报错。如果你用的是某些 CLI 工具(比如 Claude Code 类),它可能默认走 OAuth 登录而不是 API Key。这时候要在配置里显式指定 Base URL、Key、Model ID 三件套。以 Claude Code 为例,配置里需要写全:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "你的_TaoToken_Key", "ANTHROPIC_MODEL": "你确认过的模型ID" } }三件套缺一不可:Base URL 决定请求打到哪,Key 决定身份,Model ID 决定用哪个模型。只配 Key 不配 Base URL,工具会走默认端点,自然报认证失败。同理,Cline、Codex 的auth.json也要把这三项写全,别只填 Key。
Token 数对不上。如果你发现脚本统计的 tokens 和账单对不上,先确认统计的是输出 Token 还是输入加输出。脚本里len(token_times)只数了输出增量块,输入 Token 要另外从返回的 usage 里取(非流式才有完整 usage)。评测成本时两者都要算。
6. 把评测接进日常:统一 Key 的长期用法
跑通一次评测只是开始,真正有价值的是把它变成可重复的动作。我的做法是把评测脚本和配置一起放进仓库,每次模型版本更新、或者切换推理服务,就跑一次回归,对比 TTFT、ITL、TPS、准确率四条线有没有退化。配置里的模型 ID 列表就是你的评测矩阵,加一个模型只是加一行。
统一 Key 的好处在这里体现得最明显:你不需要为每个模型维护一套鉴权逻辑,评测代码只写一遍,换模型只改配置。数据集也可以复用,同一批题目打不同模型,结果才可比。如果要做更复杂的 Agent 评测,比如多轮工具调用,建议用 Coding Plan 拿更稳的额度,避免评测中途被限流打断:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。
最后给一个实用技巧:评测结果 CSV 别只看平均值,把 P50、P95、P99 都算出来。平均 TTFT 400ms 看着不错,但 P99 可能到 3 秒,那部分用户体验是崩的。用 pandas 一行就能算:
import pandas as pd df = pd.read_csv("./results/eval_result.csv") print(df.groupby("model")[["ttft_ms", "itl_ms", "tps"]].quantile([0.5, 0.95, 0.99]))把这张分位数表贴进你的选型文档,比任何“感觉挺快”都有说服力。评测链路搭好后,你会发现选模型这件事从拍脑袋变成了看数据,这才是推理基准测试真正的价值。