1. 推理延迟和成本为什么必须一起测
做机器学习推理服务时,很多人习惯把「延迟」和「成本」拆成两个独立指标看:延迟用压测工具跑个 P99,成本看月底账单。结果就是两个数字都对不上——压测时延迟很低,上线后成本却超预算;或者为了省钱把批次调大,延迟又崩了。
问题出在控制变量没固定。输入长度、批次组成、设备型号、并发到达方式,只要有一个变了,延迟和显存占用就没有可比性。你拿 A 配置的延迟去对比 B 配置的成本,得到的结论基本是错的。
这篇要解决的就是这件事:用固定输入、固定批次、固定设备三个控制变量,搭一套可复现的基准测试环境,让延迟和成本在同一条件下被同时测量。适合正在做推理服务选型、批次调优、或者要给团队出一份可复核性能报告的工程师。
核心思路是:把调度器当成待验证对象,而不是黑盒。每轮只改一个策略,记录 TTFT、排队时间、单请求完成时间、Token 消耗和设备占用时长,无法采集的项直接标空,不用经验值补齐。
2. TaoToken 前置:统一 Key 与 API 通道
在开始测量之前,先把调用通道固定下来。推理基准测试最怕的就是「这次走这个接口、下次走那个接口」,通道一变,网络往返和鉴权开销就混进延迟里了。
TaoToken 在这里的作用是提供一个统一的 Key 和 API 入口,让不同模型、不同设备的调用走同一条通道,减少变量。你可以在官网注册后拿到 Key,然后在控制台管理额度。
具体入口:
- 官网注册与总览:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=inference_bench
- API 基地址:https://taotoken.net/api
- 获取 API Key:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite&utm_content=inference_bench
- 控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite&utm_content=inference_bench
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite&utm_content=inference_bench
拿到 Key 之后,先别急着压测。用一次最小请求确认通道通,再进入正式测量。这一步能帮你排除掉「Key 没生效」「base_url 写错」这类低级问题,避免它们污染后面的延迟数据。
注意:基准测试期间建议固定同一个 Key 和同一个 base_url,不要中途切换通道,否则延迟数据不可比。
3. 可复制配置骨架:config.toml 与 settings.json
下面这套配置骨架的设计目标是:所有影响延迟和成本的变量都显式写出来,不允许隐式默认值。你可以直接复制到项目里改。
3.1 config.toml:测量控制变量
# config.toml —— 推理延迟与成本联合测量配置骨架 [model] name = "your-model-name" weight_version = "v1.0.0" # 冻结权重版本,测量期间不得变更 precision = "fp16" # fp16 / int8 / int4,必须固定 max_new_tokens = 256 [input] # 固定输入长度分布,避免长短请求混入导致不可比 prompt_tokens_list = [128, 256, 512, 1024] repeat_per_length = 20 # 每个长度重复次数 warmup_rounds = 5 # 预热轮次,不计入统计 [batch] strategy = "fixed" # serial / fixed / token_budget fixed_batch_size = 8 max_token_budget = 8192 # token_budget 策略下的单批上限 max_wait_ms = 20.0 # 组包窗口超时 [device] device_id = "gpu-0" device_model = "your-gpu-model" # 记录型号,便于跨设备对照 memory_limit_mib = 81920 [measure] record_ttft = true record_queue_time = true record_completion_time = true record_token_count = true record_device_seconds = true # 成本口径:设备占用时长 output_dir = "./bench_results"3.2 settings.json:API 通道与运行参数
{ "api": { "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY", "timeout_seconds": 120, "max_retries": 0 }, "run": { "concurrency": 1, "arrival_mode": "closed_loop", "repeat_total": 3 }, "logging": { "level": "info", "save_raw_response": true, "save_scheduler_decision": true } }几个关键点解释一下。max_retries设为 0,是因为重试会把失败请求的延迟混进成功请求的统计里,测量阶段宁可让它失败并记录。arrival_mode用closed_loop表示上一批完成才发下一批,避免并发到达方式变化引入额外变量。save_scheduler_decision打开后,每次组批的决策都会落盘,后面排查 OOM 或超时的时候能直接看。
3.3 环境变量设置
export TAOTOKEN_API_KEY="你的Key"不要把 Key 写进配置文件提交到仓库。用环境变量读取,settings.json里只留变量名。
4. 逐项验证:从通道连通到延迟成本对照
配置写好后,按顺序逐项验证。每一步只验证一件事,通过了再进下一步。
4.1 验证 API 通道连通
先用一个最小请求确认通道可用:
curl -s https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "your-model-name", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 8 }'返回里有正常的choices字段就说明通道通了。如果报 401,检查 Key 是否导出成功;如果报 404,检查 base_url 是否多了或少了路径段。
4.2 验证固定输入是否生效
跑一轮预热,确认每个长度都按repeat_per_length发满:
import json, time, os, requests with open("config.toml") as f: pass # 实际项目用 tomllib 解析 cfg = json.load(open("settings.json")) base = cfg["api"]["base_url"] key = os.environ[cfg["api"]["api_key_env"]] def one_call(prompt_tokens): payload = { "model": "your-model-name", "messages": [{"role": "user", "content": "x" * prompt_tokens}], "max_tokens": 256 } t0 = time.time() r = requests.post(f"{base}/v1/chat/completions", headers={"Authorization": f"Bearer {key}"}, json=payload, timeout=120) return time.time() - t0, r.status_code for length in [128, 256, 512, 1024]: for i in range(20): latency, code = one_call(length) print(f"len={length} round={i} latency={latency:.3f}s code={code}")跑完后检查:每个长度是否都有 20 条记录,状态码是否全为 200。有失败的直接标空,不要用平均值补。
4.3 验证批次策略切换
把batch.strategy从fixed改成token_budget,其他配置不动,重跑同一组输入。对比两轮的输出是否一致——如果输出内容不同,说明调度策略影响了结果,这本身就是需要记录的发现。
4.4 延迟与成本对照记录
每轮跑完,把关键指标写进一张对照表:
| 策略 | 批次大小 | 平均 TTFT | 平均完成时间 | 排队时间 | 总 Token | 设备占用秒 |
|---|---|---|---|---|---|---|
| serial | 1 | 待测 | 待测 | 待测 | 待测 | 待测 |
| fixed | 8 | 待测 | 待测 | 待测 | 待测 | 待测 |
| token_budget | 8192 | 待测 | 待测 | 待测 | 待测 | 待测 |
设备占用秒乘以设备单价,就是这一轮的成本口径。延迟和成本放在同一张表里,才能看出「批次调大到底省了多少、慢了多少」。
4.5 触发 OOM 时的处理
如果token_budget策略下触发显存不足,不要直接调小预算重跑。先保存输入长度摘要和调度决策日志:
grep "scheduler_decision" ./bench_results/run_*.log | tail -50看清楚是哪一批的 Token 总量超了、当时队列里有哪些请求。缩小max_token_budget后重跑,并记录这次调整前后的差异。边界必须由目标设备上的测试得到,不能靠估算。
5. 本篇常见错排查
报错一:401 UnauthorizedKey 没读到。检查TAOTOKEN_API_KEY是否在当前 shell 导出,settings.json里的变量名是否和实际一致。用echo $TAOTOKEN_API_KEY确认非空。
报错二:延迟数据波动极大大概率是预热没做够,或者测量期间有其他进程占用设备。把warmup_rounds提到 10,测量前用nvidia-smi确认没有其他任务在跑。
报错三:批次调大后输出不一致调度策略改变了请求的组批方式,可能影响生成结果。这属于正常发现,记录下来即可,不要强行对齐输出。
报错四:OOM 后排队上下文被清空显存擦边抛错时,队列里的请求会丢失。检查save_scheduler_decision是否打开,确认 OOM 前的批次 Token 总量,缩小预算后重跑。
报错五:成本口径对不上账单设备占用秒是估算口径,实际账单可能按调用量或 Token 计费。两个口径都记录,标注清楚哪个是估算、哪个是实际。
6. 继续测量与通道管理
基准测试跑通之后,日常的模型调用和 Key 管理可以走统一入口。如果你需要验证不同模型在同一输入下的表现,可以用模型对话页面快速对照:
- 模型对话:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite&utm_content=inference_bench
如果测量工作会长期做,或者要接进编码 Agent 里自动跑,Coding Plan 能减少每次手动配 Key 的重复动作:
- Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite&utm_content=inference_bench
接入过程中遇到通道或鉴权问题,直接查接入文档比翻聊天记录快:
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_campaign=rewrite&utm_content=inference_bench
最后提醒一句:测量报告里最值钱的不是那个平均延迟数字,而是「在什么输入、什么批次、什么设备下得到的这个数字」。条件写清楚了,别人才敢拿你的结论去做决策。