- 并发数配置:默认单流(1),预设2/4并发档,支持丝滑添加任意自定义并发数(≥1), 多档勾选时同一测试分别跑各并发档并并排对比 - 并发执行:每采样同时发起N个并行流(ThreadPoolExecutor),整批吞吐聚合 (多流prompt/output之和/批首字/批耗时),并记录每流明细streams; 并发=1 与旧版单流行为一致;每流独立随机前缀避免共享缓存 - 汇总:新增 by_concurrency(按并发分组,含单流均解码) + by_length_concurrency(长度x并发网格) + concurrency_levels - 详情页:新增「按并发数汇总」表 + 并发对比折线图(X=并发数,预填充左虚线/解码右实线, /concurrency-chart) - 多测试对比:历史表格勾选(可全选)多个测试 -> /api/compare 返回对比表+柱状图CSV+并发折线图CSV, 前端弹窗展示指标对比表 + 柱状图(预填充空心/解码实心) + 解码随并发折线图,画图CSV可复制/PNG可下载 - 通用图表代理 POST /api/chart(转发 data-chart-tool 请求体返回 PNG) - 历史列表增加「并发」列与勾选列;Excel 导出增加并发数列表/按并发分组/采样并发列 - 文档:README/API.md 同步更新
15 KiB
LLM 速度测试台 · API 文档
系统所有页面功能均通过 REST API 提供,前端(网页)只是这些 API 的一个可视化客户端。 任何页面可见/可操作的数据都可以通过下面的接口调用或访问。
- 服务地址:
http://<IP>:16097 - 数据格式: 请求/响应均为
application/json(导出类接口除外) - 鉴权: 内部工具,当前无鉴权;如需对外暴露,建议在网关/Nginx 层加访问控制
- 测试启动为异步:
POST /api/tests返回后,任务在后台线程执行,用GET /api/tests/<id>或日志轮询接口跟踪进度
目录
1. 健康检查
GET /api/health
返回服务状态与正在运行的测试。
响应:
{ "ok": true, "port": 16097, "running_tests": [] }
2. 提供商配置(Configs)
GET /api/configs
列出所有已保存配置(不返回 API Key 明文,仅 has_key 标记)。
响应:
[
{ "id": 2, "name": "epyc-test", "provider": "openai",
"base_url": "http://121.40.164.32:18003/v1",
"model": "unsloth/Qwen3.8-27B-Q4_K_M", "temperature": 0.7, "has_key": 1 }
]
POST /api/configs
新增配置。请求体字段:name(必填), provider, base_url, api_key, model, temperature。
请求:
{ "name": "DeepSeek", "provider": "openai",
"base_url": "https://api.deepseek.com/v1",
"api_key": "sk-xxx", "model": "deepseek-chat", "temperature": 0.7 }
响应: { "ok": true, "id": 3 }
GET /api/configs/<id>
获取单个配置(含 API Key 明文,用于前端回填;注意保管)。
PUT /api/configs/<id>
更新配置,局部更新(只改请求里出现的字段)。
请求: { "model": "deepseek-v4-flash" } → 响应 { "ok": true, "id": 3 }
DELETE /api/configs/<id>
删除配置。响应 { "ok": true }
3. 连接测试
POST /api/configs/test
验证 API Key / Base URL / 模型名连通性。只要流式请求成功返回(HTTP 200 + 收到响应流)即视为连通;支持推理型模型(Qwen3/DeepSeek 思维链)。
请求: 同配置对象(provider, base_url, api_key, model, temperature)
成功响应:
{
"ok": true, "total_ms": 1308.2,
"note": "",
"metrics": { "prompt_tokens": 63, "output_tokens": 32, "output_chars": 110,
"ttft_ms": 715.9, "prefill_speed": 86.7, "decode_speed": 52.7, "total_ms": 1308.2 }
}
note非空表示连接正常但未返回正文(可能为只输出思维链的模型)。
失败响应: { "ok": false, "error": "HTTP 401: ..." }
4. 速度测试(Tests)
POST /api/tests
启动一次速度测试(异步,立即返回测试 id)。
请求体:
{
"config": {
"provider": "openai",
"name": "Qwen3 对比",
"base_url": "http://121.40.164.32:18003/v1",
"api_key": "sk-xxx",
"model": "unsloth/Qwen3.8-27B-Q4_K_M",
"temperature": 0.7
},
"gen": {
"name": "Qwen3 不同上下文长度速度对比",
"context_lengths": [512, 2048, 4096, 8192, 16384, 32768, 65536, 131072],
"max_tokens": 128,
"samples": 2,
"warmup": true,
"avoid_cache": true
}
}
gen 字段说明:
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
name |
string | "" |
测试名称/主题(会存入测试记录并展示在历史与详情) |
context_lengths |
number[] | [512,2048,4096,8192,16384,32768,65536,131072] |
要测试的上下文长度列表,每个长度独立校准+预热+采样 |
max_tokens |
number | 128 |
解码输出 token 长度 |
samples |
number | 2 |
每个(长度×并发)组合的采样次数 |
concurrency_levels |
number[] | [1] |
并发数列表(默认单流)。>1 时每采样同时发起 N 个并行流,聚合为整批吞吐指标;多档自动并排对比 |
warmup |
bool | true |
测试前空转预热(不计速度,按并发数预热) |
avoid_cache |
bool | true |
随机前缀避免缓存命中(每个并发流独立前缀) |
响应: { "ok": true, "id": 9 }
GET /api/tests?limit=<n>
测试历史列表(按 id 倒序)。limit 默认 100,最大 1000。
响应:
[
{ "id": 9, "created_at": "2026-08-23 18:52:00", "status": "done",
"provider": "openai", "model": "unsloth/Qwen3.8-27B-Q4_K_M",
"name": "Qwen3 不同上下文长度速度对比",
"error": "",
"summary": { "samples_ok": 2, "samples_total": 2, "avg_ttft_ms": 1808.7, ... } }
]
POST /api/tests/<id>/cancel
停止正在运行的测试。响应 { "ok": true, "msg": "正在停止..." }
DELETE /api/tests/<id>
删除测试及其全部采样与日志。响应 { "ok": true }
5. 测试详情与日志
GET /api/tests/<id>
完整测试详情:基本信息 + 配置(API Key 已打码)+ 生成参数 + 汇总 + 每次采样 + 完整日志。
响应结构:
{
"id": 9, "created_at": "...", "status": "done",
"provider": "openai", "model": "...", "name": "...", "error": "",
"config": { "base_url": "...", "api_key": "sk-x****", ... },
"gen": { "name": "...", "context_lengths": [512, 2048], "max_tokens": 128, "samples": 1, "concurrency_levels": [1, 2, 4], "warmup": true, "avoid_cache": true },
"summary": {
"samples_total": 6, "samples_ok": 6,
"concurrency_levels": [1, 2, 4],
"calibration_chars_per_token": 1.82,
"avg_ttft_ms": 1808.7, "min_ttft_ms": 1122.8, "max_ttft_ms": 2494.6,
"avg_prefill_speed": 694.2, "min_prefill_speed": 515.7, "max_prefill_speed": 872.7,
"avg_decode_speed": 54.7, "min_decode_speed": 54.4, "max_decode_speed": 55.0,
"avg_prompt_tokens": 1378.0, "avg_output_tokens": 128.0,
"avg_total_ms": 4148.4, "min_total_ms": 3449.9, "max_total_ms": 4846.9,
"by_length": {
"512": { "samples_total": 3, "samples_ok": 3, "avg_ttft_ms": 1122.8, "avg_prefill_speed": 515.7, "avg_decode_speed": 55.0, "avg_prompt_tokens": 579, "avg_output_tokens": 128, "avg_total_ms": 3449.9 },
"2048": { "samples_total": 3, "samples_ok": 3, "avg_ttft_ms": 2494.6, "avg_prefill_speed": 872.7, "avg_decode_speed": 54.4, "avg_prompt_tokens": 2177, "avg_output_tokens": 128, "avg_total_ms": 4846.9 }
},
"by_concurrency": {
"1": { "samples_total": 2, "samples_ok": 2, "avg_ttft_ms": 1122.8, "avg_prefill_speed": 515.7, "avg_decode_speed": 55.0, "avg_stream_decode": 55.0, "avg_prompt_tokens": 579, "avg_output_tokens": 128, "avg_total_ms": 3449.9 },
"2": { "samples_total": 2, "samples_ok": 2, "avg_ttft_ms": 2494.6, "avg_prefill_speed": 872.7, "avg_decode_speed": 108.0, "avg_stream_decode": 54.0, "avg_prompt_tokens": 1158, "avg_output_tokens": 256, "avg_total_ms": 4846.9 }
},
"by_length_concurrency": { "512": { "1": {...}, "2": {...} }, "2048": {...} }
},
"runs": [
{ "run_index": 1, "context_length": 512,
"metrics": { "prompt_tokens": 579, "output_tokens": 128, "cached_tokens": 0,
"ttft_ms": 1122.8, "prefill_speed": 515.7, "decode_speed": 55.0, "total_ms": 3449.9 },
"error": "" }
],
"logs": [ { "id": 1, "level": "INFO", "msg": "═══ 开始速度测试 ═══", "rel": 0.0, "ts": "..." } ]
}
GET /api/tests/<id>/logs?after=<id>
增量日志(前端轮询用)。after 为上次取到的最大日志 id,返回其后新增日志 + 最新状态/汇总/最后采样。
响应:
{
"status": "running", "error": "",
"summary": {},
"last_run": { "...": "..." },
"logs": [ { "id": 68, "level": "METRIC", "msg": "...", "rel": 3.21 } ],
"after": 73
}
6. 折线图(data-chart-tool)
调用 data-chart-tool 的
/api/chart接口生成双Y轴折线图:左轴=预填充速度(虚线)、右轴=解码速度(实线)、X 轴=上下文长度。本系统在服务端代理转发,前端只需请求本服务的两个接口。
GET /api/tests/<id>/chart
生成折线图 PNG 图片(内部调用 data-chart-tool /api/chart,地址可在 config.py 的 CHART_API_BASE 修改)。
成功响应: Content-Type: image/png(可直接用于 <img> / 下载)
失败响应:
{ "ok": false, "error": "无成功采样数据,无法画图" } // 400
{ "ok": false, "error": "测试不存在" } // 404
{ "ok": false, "error": "图表服务不可用: ..." } // 502
GET /api/tests/<id>/chart-data
返回画图所用的 CSV 数据与图表请求配置,方便手动快速复制。
响应:
{
"ok": true,
"csv": "上下文长度, 预填充速度(tok/s), 解码速度(tok/s)\n4096, 126.90, 22.80\n8192, 132.20, 22.40",
"rows": [[4096, 126.9, 22.8], [8192, 132.2, 22.4]],
"payload": {
"data": "...", "chartType": "line", "title": "...",
"dualYAxis": true,
"leftAxisName": "预填充速度(tok/s)", "rightAxisName": "解码速度(tok/s)",
"seriesTypes": ["line", "line"], "seriesAxis": [0, 1],
"seriesStyles": ["dashed", "solid"], "width": 1000, "height": 560, "pixelRatio": 2
}
}
csv即画图数据(第一列=上下文长度,第二列=预填充速度,第三列=解码速度),前端「复制画图数据」按钮复制的就是它。
GET /api/tests/<id>/concurrency-chart
并发对比折线图 PNG(X 轴=并发数,左轴=预填充速度虚线、右轴=解码速度实线),用于直观展示吞吐随并发的变化。仅当本次测试包含多个并发档时才有数据。
失败响应: { "ok": false, "error": "无并发分组采样数据(本次测试可能只测了单流),无法画图" }(400)
GET /api/tests/<id>/concurrency-chart-data
并发对比画图数据(CSV + 图表请求配置)。
{
"ok": true,
"csv": "并发数, 预填充速度(tok/s), 解码速度(tok/s)\n1, 767.30, 43.40\n2, 1388.20, 70.90\n4, 2085.00, 78.70",
"rows": [[1, 767.3, 43.4], [2, 1388.2, 70.9], [4, 2085.0, 78.7]],
"payload": { "data": "...", "chartType": "line", "title": "...", "dualYAxis": true, ... }
}
POST /api/chart
通用图表代理:把任意 data-chart-tool /api/chart 请求体转发过去并返回 PNG(多测试对比面板用)。请求体即 data-chart-tool 的参数(data/chartType/seriesTypes/seriesStyles/dualYAxis…)。
成功响应: Content-Type: image/png
POST /api/compare
把多个测试结果放在一起对比。
请求: { "ids": [9, 10, 11] }(最多 20 个)
响应:
{
"ok": true,
"rows": [ { "id": 9, "label": "#9 xxx", "created_at": "...", "name": "...", "model": "...",
"concurrency_levels": [1, 2, 4], "by_concurrency": {...},
"samples_ok": 6, "samples_total": 6,
"avg_ttft_ms": 1122.8, "avg_prefill_speed": 515.7, "avg_decode_speed": 55.0,
"avg_stream_decode": 55.0, "avg_output_tokens": 128, "avg_total_ms": 3449.9 } ],
"bar_csv": "测试, 预填充速度(tok/s), 解码速度(tok/s)\n#9 xxx, 515.70, 55.00",
"bar_payload": { "data": "...", "chartType": "bar", "seriesTypes": ["bar", "bar"], "seriesStyles": ["hollow", "solid"], ... },
"line": { // 仅当所选测试存在 >=2 个共同并发档时返回,否则为 null
"csv": "并发数, #9 xxx, #10 yyy\n1, 55.00, 52.10\n2, 108.00, 99.30\n4, 190.20, 175.60",
"payload": { "data": "...", "chartType": "line", ... },
"levels": [1, 2, 4]
}
}
7. 导出(Excel / JSON)
GET /api/tests/<id>/export.xlsx
导出 Excel 报告(3 个 Sheet:汇总 / 采样明细 / 日志),Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet。
- 汇总:测试信息(含并发数列表)+ 整体统计指标(平均/最大/最小)+ 按上下文长度分组 + 按并发数分组(整批吞吐,含单流均解码)
- 采样明细:每次采样的上下文长度、并发、流成功/总数与全部指标
- 日志:完整测试日志
GET /api/tests/<id>/export.json
导出完整测试数据为 JSON(与 GET /api/tests/<id> 一致,API Key 打码),Content-Type: application/json。
8. 数据模型
| 表 | 说明 | 关键字段 |
|---|---|---|
configs |
保存的接口配置 | id, name, provider, base_url, api_key, model, temperature |
tests |
测试记录 | id, status(running/done/error/canceled), provider, model, name, config_json, gen_cfg_json, summary_json, error |
test_runs |
每次采样 | id, test_id, run_index, context_length, metrics_json, error(metrics 含 concurrency/streams_total/streams_ok/avg_stream_decode/streams(每流明细)) |
logs |
测试日志 | id, test_id, level, msg, rel |
summary 整体指标字段:
avg_/min_/max_ 前缀 × ttft_ms / prefill_speed / decode_speed / total_ms,以及 avg_prompt_tokens / avg_output_tokens / avg_cached_tokens / best_ttft_ms(= min_ttft_ms)。
分组字段: by_length(按上下文长度)、by_concurrency(按并发数,含 avg_stream_decode 单流均解码)、by_length_concurrency(长度×并发全网格)、concurrency_levels(本次测试的并发档列表)。
9. curl 使用示例
BASE=http://<IP>:16097
# 健康检查
curl $BASE/api/health
# 新增配置
curl -X POST $BASE/api/configs -H 'Content-Type: application/json' \
-d '{"name":"Qwen3","provider":"openai","base_url":"http://121.40.164.32:18003/v1","api_key":"sk-xxx","model":"unsloth/Qwen3.8-27B-Q4_K_M","temperature":0.7}'
# 测试连接
curl -X POST $BASE/api/configs/test -H 'Content-Type: application/json' \
-d '{"provider":"openai","base_url":"http://121.40.164.32:18003/v1","api_key":"sk-xxx","model":"unsloth/Qwen3.8-27B-Q4_K_M"}'
# 启动速度测试(异步,含多并发档)
curl -X POST $BASE/api/tests -H 'Content-Type: application/json' -d '{
"config": {"provider":"openai","base_url":"http://121.40.164.32:18003/v1","api_key":"sk-xxx","model":"unsloth/Qwen3.8-27B-Q4_K_M"},
"gen": {"name":"并发对比","context_lengths":[2048],"max_tokens":128,"samples":2,"concurrency_levels":[1,2,4],"warmup":true,"avoid_cache":true}
}'
# 查询测试列表 / 详情
curl "$BASE/api/tests?limit=10"
curl $BASE/api/tests/9
# 画图数据(CSV)
curl $BASE/api/tests/9/chart-data
# 折线图 PNG(预填充左轴虚线 / 解码右轴实线,X=上下文长度)
curl -o chart.png $BASE/api/tests/9/chart
# 并发对比折线图(X=并发数)
curl -o cc.png $BASE/api/tests/9/concurrency-chart
# 多测试对比
curl -X POST $BASE/api/compare -H 'Content-Type: application/json' -d '{"ids":[9,10,11]}'
# 导出
curl -OJ $BASE/api/tests/9/export.xlsx
curl $BASE/api/tests/9/export.json