☰
如何对LLM大型语言模型进行评估与基准测试:从性能指标到推理能力的落地实践
2026/10/26 12:31:47 网站建设 项目流程

1. 为什么你的 LLM 选型总在“跑分好看、上线翻车”之间反复横跳

很多团队在给大型语言模型做评估时,第一反应是去翻某个榜单,看谁家分数高就选谁。结果模型接进业务后,用户抱怨“回答半天蹦不出第一个字”,或者高峰期请求排队到超时,再或者同一段提示词今天答得挺好、明天就开始胡言乱语。问题不在于模型本身不行,而在于评估口径和真实场景脱节了。

LLM 评估与基准测试,本质上是回答三个问题:这个模型在我的输入输出长度分布下,首 Token 要等多久、每秒能吐多少 Token、连续跑几千个请求会不会崩。它适合需要为模型选型与上线把关的工程团队,也适合正在做推理成本优化的后端同学。你不需要一上来就搭一套庞大的压测集群,但必须有一套可复现的配置:固定的数据集、固定的评测脚本、固定的指标口径,否则不同工具跑出来的数字根本没法横向比。

我见过最常见的坑是拿负载测试的结论去做性能基准测试的判断。负载测试模拟的是一群用户同时访问,关心的是“顶得住多少人”;性能基准测试专注模型本身的吞吐和延迟,关心的是“跑得有多快”。两者混着做,就会出现“并发调高后 TPS 涨了,但单用户 TPS 掉了一半”这种看似矛盾、实则正常的现象。所以这篇内容会从指标定义讲起,一路落到可复制的配置文件和验证脚本,并且用统一的 API 通道接入被测模型,保证你换模型时只改一个 Model ID,不用重写整套评测代码。

2. 用 TaoToken 统一 Key 与 API 通道,把被测模型接进评测脚本

做基准测试最烦的事情之一,是每换一个模型就要改一遍 SDK、换一套鉴权、对一遍 Base URL。为了让评测脚本保持稳定,我习惯把所有被测模型收敛到同一个 OpenAI 兼容通道上,这样评测代码里只变model字段,其余逻辑完全不动。TaoToken 在这里扮演的就是这个统一入口:官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 可以了解整体能力,API 地址是 https://taotoken.net/api,兼容 OpenAI 的/v1/chat/completions形态,流式和非流式都支持。

前置准备其实就三步。第一步,在控制台创建一个 API Key,建议按评测项目单独建 Key,方便后面看调用量和排障。第二步,确认你要测的模型 ID,不同模型的上下文长度和最大输出 Token 数不一样,这个参数会直接影响你的 ISL/OSL 设定。第三步,把 Base URL 和 Key 写进环境变量,别硬编码在脚本里,后面跑多轮对比时你会感谢自己。

export TAOTOKEN_BASE_URL="https://taotoken.net/api/v1" export TAOTOKEN_API_KEY="sk-你的评测专用Key"

这里有个细节值得展开:评测脚本里一定要显式设置stream: true和stream_options: {"include_usage": true}。前者让你能测到 TTFT(首 Token 时间),后者让你在流式结束后拿到准确的 prompt_tokens 和 completion_tokens,避免自己数 Token 数出偏差。很多工具在统计 TPS 时口径不一致,就是因为有的把请求准备时间算进去了,有的没算,自己拿 usage 字段算最稳。

另外提醒一句,评测用的 Key 和线上业务 Key 分开,别拿生产 Key 去跑几千次压测,既不好归因也容易触发限流策略。控制台里可以给 Key 加备注,写上“LLM-bench-2025Q1”这类标识,后面回看调用记录时一目了然。如果你要测的是长期编码或 Agent 类场景,可以顺带看一下 Coding Plan 的额度形态,避免评测跑到一半额度不够中断,导致样本量不完整。

3. 可复制的基准测试配置:数据集、评测脚本与指标口径

这一节是整篇的核心,我会给出一份可以直接落地的配置。先说数据集。你不需要去找什么公开榜单,最贴近业务的做法是从真实日志里采样 200 到 500 条请求,按输入长度分桶:短输入(约 100 Token,比如分类、意图识别)、中等输入(约 500 到 2000 Token,比如翻译、摘要)、长输入(1000 Token 以上,比如 RAG 检索增强后的问答)。输出长度也要标注预期,生成类任务 OSL 可能到 1000 以上,摘要类任务 OSL 通常只有 100 左右。

把数据集存成 JSONL,每行一条,字段包括id、prompt、expected_output_tokens、bucket。这样评测脚本可以按桶统计,而不是只给一个全局平均数——全局平均最容易被长尾请求带偏。

{"id": "case-001", "bucket": "short", "prompt": "把下面这句话翻译成英文:今天天气很好。", "expected_output_tokens": 20} {"id": "case-002", "bucket": "medium", "prompt": "请总结以下会议纪要的三个关键决策点:……", "expected_output_tokens": 150} {"id": "case-003", "bucket": "long", "prompt": "基于以下检索文档回答用户问题:……", "expected_output_tokens": 300}

接下来是评测脚本。用 Python 写一个最小可用的 runner,核心逻辑是:对每条样本发起流式请求,记录请求发出时间、首个非空 chunk 到达时间、最后一个 chunk 到达时间,以及 usage 里的 completion_tokens。TTFT 就是首个 chunk 时间减去请求发出时间;ITL 用(最后 chunk 时间减首个 chunk 时间)除以(completion_tokens 减 1);端到端延迟就是最后 chunk 时间减请求发出时间。注意要过滤掉空字符串的初始响应,否则 TTFT 会被污染成接近 0 的假值。

import os, time, json, statistics from openai import OpenAI client = OpenAI( base_url=os.environ["TAOTOKEN_BASE_URL"], api_key=os.environ["TAOTOKEN_API_KEY"], ) def run_one(prompt, model="你的模型ID", max_tokens=512): t0 = time.perf_counter() first_token_at = None last_chunk_at = None usage = None stream = client.chat.completions.create( model=model, messages=[{"role": "user", "content": prompt}], max_tokens=max_tokens, temperature=0, stream=True, stream_options={"include_usage": True}, ) for chunk in stream: now = time.perf_counter() if chunk.choices and chunk.choices[0].delta.content: if first_token_at is None: first_token_at = now last_chunk_at = now if chunk.usage: usage = chunk.usage completion_tokens = usage.completion_tokens if usage else 0 ttft = (first_token_at - t0) if first_token_at else None e2e = (last_chunk_at - t0) if last_chunk_at else None itl = None if first_token_at and last_chunk_at and completion_tokens > 1: itl = (last_chunk_at - first_token_at) / (completion_tokens - 1) return {"ttft": ttft, "e2e": e2e, "itl": itl, "completion_tokens": completion_tokens}

指标口径要提前定死,写进 README,避免不同人跑出不同结论。我建议的口径是:TTFT 取 P50 和 P95,ITL 取均值,TPS 用总 completion_tokens 除以从第一个请求发出到最后一个响应完成的墙钟时间,RPS 用成功请求数除以同一段时间。采样参数统一用temperature=0(Greedy),并且设置ignore_eos等价行为——也就是把max_tokens设成你期望的固定长度,让每次输出长度一致,否则短回答会把 ITL 拉低、长回答会把 ITL 拉高,横向对比就失真了。

并发控制建议从 1 开始,逐步加到略高于推理引擎的最大批处理大小。你可以用concurrency参数控制同时活跃的请求数,每完成一个就补一个,保证系统里始终有 N 个在途请求。这个方式比固定请求速率更稳,因为速率控制下如果系统处理不过来,未完成请求会无限堆积,最后测出来的延迟全是排队时间。

4. 验证请求与成功结果:跑分对比、延迟复测与稳定性观察

配置写好后,先做一次单并发冒烟验证,确认通道和脚本都通。跑一条样本,打印 TTFT、ITL、completion_tokens,看数值是否在合理范围。如果 TTFT 是几百毫秒到一两秒、ITL 在几十毫秒量级,说明链路正常。这一步别省,我踩过的坑就是流式开关没生效,结果 TTFT 等于端到端延迟,白跑了一轮。

冒烟通过后,按桶跑全量数据集,每个桶至少 100 条样本,记录 P50/P95 的 TTFT 和端到端延迟。然后做并发阶梯测试:并发 1、4、8、16,每个档位跑 3 分钟或跑完固定样本量,观察 TPS 是否随并发上升后趋于饱和、单用户 TPS 是否随并发上升而下降。这个交叉点就是你系统的“甜点并发”,超过它再加并发只会让延迟变差、吞吐不涨。

稳定性复测建议至少跑 30 分钟连续请求,观察 ITL 是否随时间漂移。如果 ITL 在前 10 分钟稳定、后面逐渐变大,通常是 KV Cache 增长或显存碎片导致的,这时候要回头看你的最大上下文长度设置是否合理。另一个观察点是错误率:401、429、超时分别计数,429 说明触发了限流,需要降低请求速率或申请更高额度;超时则要区分是网络问题还是模型生成卡住。

成功结果的判定标准我一般设三条:目标桶的 P95 TTFT 低于业务可接受阈值(比如对话场景 2 秒内)、ITL 均值稳定且无随时间上升趋势、连续 30 分钟错误率低于 1%。三条都过,才进入下一轮模型对比。对比时保持数据集、采样参数、并发档位完全一致,只换 Model ID,这样跑出来的差异才能归因到模型本身。

5. 本篇常见错排查:401、local proxy failed、reading choices 与 OAuth 报错

评测跑不起来,八成是下面这几类报错。逐个说清楚现象和修法。

401 Unauthorized:最常见。先确认TAOTOKEN_API_KEY环境变量真的被读到了,别出现 shell 里 export 了但 Python 进程没继承的情况。其次检查 Key 有没有多余空格或换行,复制粘贴时很容易带上。如果 Key 确认无误还是 401,去控制台看这个 Key 是否被禁用或额度耗尽。注意 Base URL 要带/v1,写成https://taotoken.net/api/v1,少写/v1有些客户端会拼出错误路径,返回的也可能是 401 或 404。

local proxy failed / connection refused:这类报错通常出现在客户端配置了本地代理但代理没启动,或者环境变量里残留了HTTP_PROXY、HTTPS_PROXY指向一个不存在的端口。排查方法是先unset掉这些变量再跑一次,确认是不是代理配置问题。评测脚本里建议显式不传代理参数,保持直连,避免中间层干扰延迟测量。

reading choices 相关报错:典型信息是KeyError: 'choices'或list index out of range,发生在解析响应时。原因一般是请求失败但脚本没检查状态码就直接取choices[0],或者流式模式下某个 chunk 的choices是空数组(比如最后一个只带 usage 的 chunk)。修法是解析前先判断if chunk.choices and len(chunk.choices) > 0,并且对非流式响应先看response.choices是否存在。另外确认max_tokens没有设成 0 或负数,否则可能返回空 choices。

OAuth / token 过期类报错:如果你用的是某些需要 OAuth 流程的客户端(比如 Claude Code 这类工具),报错信息里会出现 token 刷新失败或授权失效。这类问题的根因通常是客户端缓存了旧的凭证。处理方式是清理客户端配置目录下的凭证缓存,重新走一次授权。如果你是通过 API Key 方式接入,就不涉及 OAuth,直接检查 Key 即可。这里要强调三件套必须写全:Base URL、API Key、Model ID,缺一个都会在鉴权或路由阶段报错,而且报错信息往往不直接指向缺失项,容易绕弯路。

还有一个隐蔽的坑:并发调高后出现大量超时,但单条请求手动跑又正常。这通常是客户端连接池太小,或者没复用 HTTP 连接导致每次请求都重新握手。把客户端超时时间调大、连接池调大,再复测一轮,往往就恢复了。

6. 把评测固化成流程:从一次性跑分到持续回归

一次性跑分只能回答“今天这个模型行不行”,回答不了“下周换了个版本还行不行”。所以最后一步是把上面这套东西固化成可重复执行的流程。我的做法是把数据集、runner 脚本、指标计算和阈值判断打包成一个目录,用一条命令跑完,输出一份 JSON 报告,里面按桶列出 TTFT P50/P95、ITL 均值、TPS、RPS 和错误率。每次模型切换或配置变更都跑一遍,和历史报告做 diff,超过阈值波动就告警。

具体落地时,报告里加一个config_hash字段,把 Base URL、Model ID、max_tokens、temperature、并发档位拼起来做哈希。这样你回看历史报告时,能一眼看出两次跑分是不是同一套配置,避免拿不同口径的数字互相比较。数据集也要版本化,新增样本就升一个版本号,老报告保留,保证可追溯。

如果你要测的是编码或 Agent 这类长会话场景,建议单独建一个桶,输入是短指令、输出是长代码或长推理链,OSL 设到 1000 以上,专门观察 ITL 在长输出下是否稳定。这类场景对首 Token 时间不敏感,但对持续生成速度和上下文增长后的性能衰减很敏感。跑完对比后,把结论沉淀成选型文档:哪个模型在哪个桶里达标、成本大概什么量级、什么并发下开始劣化。下次有人问“为什么选这个模型”,你直接把报告甩过去就行。

评测这件事,工具和指标都是次要的,关键是口径统一、配置可复现、结论能归因。把这三件事做到,你就不用在“跑分好看”和“上线翻车”之间反复横跳了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询