Files
hz4th_coder 35ac2a8e40 v2.3.0 多并发测试 + 多测试结果对比
- 并发数配置:默认单流(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 同步更新
2026-09-01 19:33:41 +08:00

372 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LLM 速度测试台 · API 文档
> 系统所有页面功能均通过 REST API 提供,前端(网页)只是这些 API 的一个可视化客户端。
> 任何页面可见/可操作的数据都可以通过下面的接口调用或访问。
- **服务地址:** `http://<IP>:16097`
- **数据格式:** 请求/响应均为 `application/json`(导出类接口除外)
- **鉴权:** 内部工具,当前无鉴权;如需对外暴露,建议在网关/Nginx 层加访问控制
- **测试启动为异步**`POST /api/tests` 返回后,任务在后台线程执行,用 `GET /api/tests/<id>` 或日志轮询接口跟踪进度
---
## 目录
1. [健康检查](#1-健康检查)
2. [提供商配置](#2-提供商配置-configs)
3. [连接测试](#3-连接测试)
4. [速度测试](#4-速度测试-tests)
5. [测试详情与日志](#5-测试详情与日志)
6. [折线图(data-chart-tool](#6-折线图data-chart-tool)
7. [导出(Excel / JSON](#7-导出excel--json)
8. [数据模型](#8-数据模型)
9. [curl 使用示例](#9-curl-使用示例)
---
## 1. 健康检查
### `GET /api/health`
返回服务状态与正在运行的测试。
**响应:**
```json
{ "ok": true, "port": 16097, "running_tests": [] }
```
---
## 2. 提供商配置(Configs
### `GET /api/configs`
列出所有已保存配置(不返回 API Key 明文,仅 `has_key` 标记)。
**响应:**
```json
[
{ "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`
**请求:**
```json
{ "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`
**成功响应:**
```json
{
"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)。
**请求体:**
```json
{
"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。
**响应:**
```json
[
{ "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 已打码)+ 生成参数 + 汇总 + 每次采样 + 完整日志。
**响应结构:**
```json
{
"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,返回其后新增日志 + 最新状态/汇总/最后采样。
**响应:**
```json
{
"status": "running", "error": "",
"summary": {},
"last_run": { "...": "..." },
"logs": [ { "id": 68, "level": "METRIC", "msg": "...", "rel": 3.21 } ],
"after": 73
}
```
---
## 6. 折线图(data-chart-tool
> 调用 [data-chart-tool](http://192.168.2.8:12007/hz4th_coder/data-chart-tool.git) 的 `/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>` / 下载)
**失败响应:**
```json
{ "ok": false, "error": "无成功采样数据,无法画图" } // 400
{ "ok": false, "error": "测试不存在" } // 404
{ "ok": false, "error": "图表服务不可用: ..." } // 502
```
### `GET /api/tests/<id>/chart-data`
返回画图所用的 CSV 数据与图表请求配置,方便手动快速复制。
**响应:**
```json
{
"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 + 图表请求配置)。
```json
{
"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 个)
**响应:**
```json
{
"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, errormetrics 含 `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 使用示例
```bash
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
```