# bench **Repository Path**: jeff1227/bench ## Basic Information - **Project Name**: bench - **Description**: 模型压缩 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-07 - **Last Updated**: 2026-08-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # bench - 大模型压测工具 一个使用 Go 语言编写的命令行工具,用于对云厂商 LLM(大语言模型)服务进行性能压测,输出 QPS、RPM、TPM、TTFT、TPOT、延迟分布等关键指标,并生成可视化 HTML 报告。 ## 特性 - **多协议支持**:OpenAI 兼容(`/v1/chat/completions`)、Anthropic(`/v1/messages`)、通用 HTTP - **流式 & 非流式**:支持 SSE 流式响应,可精确测量 TTFT(首 token 时间)与 TPOT(每 token 间隔) - **可配置上下文长度**:支持 1k / 8k 两种规格 - **可配置并发数**:通过 `--concurrency` 调整 - **预热机制**:通过 `--warmup` 在正式测试前发送预热请求,避免首请求偏差 - **HTML 报告**:自动生成包含核心指标卡片、延迟分布、状态码分布的可视化报告 - **零外部依赖**:仅使用 Go 标准库 ## 编译 ```bash go build -o bench ./cmd/bench ``` ## 快速开始 ```bash # OpenAI 兼容接口压测 ./bench \ --url https://api.openai.com/v1/chat/completions \ --model gpt-4 \ --api-key sk-xxx \ --protocol openai \ --concurrency 10 \ --requests 100 \ --context-length 1k \ --stream \ --output report.html ``` ```bash # Anthropic Claude 压测 ./bench \ --url https://api.anthropic.com/v1/messages \ --model claude-3-5-sonnet-20241022 \ --api-key sk-ant-xxx \ --protocol anthropic \ --concurrency 5 \ --requests 50 \ --context-length 8k \ --stream \ --output claude_report.html ``` ```bash # 通用 HTTP 压测 ./bench \ --url https://api.example.com/v1/completions \ --model custom-model \ --api-key my-key \ --protocol generic \ --concurrency 20 \ --requests 200 \ --context-length 1k \ --stream=false \ --output custom_report.html ``` API Key 也可通过环境变量提供:`API_KEY`、`OPENAI_API_KEY`、`ANTHROPIC_API_KEY`。 ## 命令行参数 | 参数 | 说明 | 默认值 | |---|---|---| | `--url` | API endpoint URL(必填) | - | | `--model` | 模型名称(必填) | - | | `--api-key` | API Key(必填) | - | | `--protocol` | 协议类型:`openai` / `anthropic` / `generic` | `openai` | | `--concurrency` | 并发数 | `10` | | `--requests` | 总请求数 | `100` | | `--context-length` | 上下文长度:`1k` / `8k` | `1k` | | `--max-output-tokens` | 单次输出最大 token 数 | `256` | | `--stream` | 是否使用流式响应 | `true` | | `--output` | HTML 报告输出路径(留空则按 `output/YYYYMMDDHHMM/report.html` 自动生成) | `output/YYYYMMDDHHMM/report.html` | | `--warmup` | 预热请求数(不计入统计) | `0` | | `--timeout` | 单次请求超时(支持 duration:`30s`/`2m`/`1h`) | `120s` | | `--chars-per-token` | 每个 token 估算字符数;中文偏小(2~3),英文 4 | `4` | ## 压测策略:如何设置 `--concurrency` 与 `--requests` `--concurrency`(并发数)决定施加的**压力强度**,`--requests`(总请求数)决定统计的**样本量**。 调度逻辑是:总共 `requests` 个任务,用 `concurrency` 个槽位并发执行,完成一个补下一个,直到发完。 因此 `requests` 应显著大于 `concurrency`(建议 **10~20 倍**),否则 P95/P99 分位数没有统计意义。 > 无论哪种目标,都建议先用 `--concurrency 2 --requests 10` 跑通(确认 URL / key / model 正确、 > 成功率 100%),再进入正式压测,避免浪费额度。 ### 目标一:找并发上限(阶梯加压) 本工具每次运行只跑**一个固定并发档**,找上限需要固定 `requests`、逐档抬高 `concurrency`、多轮对比: ```bash ./bench ... --warmup 5 --concurrency 5 --requests 200 --output out/c05.html ./bench ... --warmup 5 --concurrency 10 --requests 300 --output out/c10.html ./bench ... --warmup 5 --concurrency 20 --requests 400 --output out/c20.html ./bench ... --warmup 5 --concurrency 40 --requests 400 --output out/c40.html ./bench ... --warmup 5 --concurrency 80 --requests 400 --output out/c80.html ``` 对比每轮结果,出现以下任一信号即说明达到上限(可用二分法收敛,如 40 正常、80 出 429 则再试 60): - **RPM / TPM 涨不动**:并发翻倍但吞吐基本不变,服务已饱和,再加并发只会堆积延迟。 - **状态码分布出现 429**:触发限流。压 Anthropic / OpenAI 官方端点时,这通常是**账户配额上限**而非模型算力上限。 - **P95 / P99 延迟陡增**:吞吐没涨但延迟飙升,同样是饱和信号。 ### 目标二:固定负载测延迟 / 吞吐 从上一步找到的稳定并发(成功率 100%、无 429 的最高档)中挑一个贴近真实业务的并发数,跑一轮**大样本**: ```bash ./bench --url https://api.anthropic.com/v1/messages --model --api-key \ --protocol anthropic --warmup 10 --concurrency 20 --requests 500 \ --context-length 8k --stream --output out/steady.html ``` 重点关注 HTML 报告中的延迟分布(P50/P95/P99 Latency、TTFT、TPOT)与吞吐(RPM、TPM、输出 tok/s)。 需注意 **`--stream` 才能得到有意义的 TTFT / TPOT**。 ### 参数要点 - **`--warmup`**:建议开(5~10),预热请求不计入统计,避免冷启动 / 首次建连拉高 P99。 - **`--requests` 给够**:至少并发的 10~20 倍,分位数才可靠。 - **`--context-length` 每轮保持一致**:不同输入长度的结果不可横向对比;测真实场景就固定为业务输入规格。 - **`--timeout`**:默认 120s。高并发下大量请求排队超时会计入网络错误,注意区分「服务真慢」与「并发过高导致自己排队」。 ## 报告内容 HTML 报告包含以下模块: - **核心指标卡片**:QPS、成功率、TTFT P50/P95/P99、平均 TPOT、输出吞吐、总输出 tokens - **Run Summary 卡片**:每次压测的总请求 / 成功 / 失败 / 并发 / Time Taken / Avg Latency / Avg TTFT / Avg TPOT / Avg ITL 等关键数值 - **延迟分布表**:平均 / 最小 / 最大 / P50 / P90 / P95 / P99 延迟(含条形图) - **请求汇总**:总请求数、成功数、失败数、网络错误数、总输出 tokens、平均输出 tokens - **HTTP 状态码分布**:按具体状态码(如 200 / 401 / 404 / 429 / 500)着色分行显示占比 - **RPM / TPM**:每分钟请求数与每分钟 token 数(Run Summary 卡片) - **Network Errors**:独立于 HTTP 状态码的网络层错误(连接失败、超时、ctx 取消等),不污染状态码分布 > 终端结果区同样会打印 **RPM / TPM** 以及**具体状态码分布**(如 `状态码分布: 200=17, 404=3, 网络错误=2`), > 失败时还会额外输出 `状态码明细: 401=600` 便于快速定位鉴权 / 路径等根因。 ## 指标说明 > 下列速率指标的「总耗时」均指**正式压测阶段的墙钟时间**(从正式请求开始到聚合), > **不含 `--warmup` 预热阶段**,确保预热不会拉低 QPS/RPM/TPM/吞吐。 **统计口径(重要)**:指标分两类—— > - **负载类**(全样本,含失败请求):总请求数、RPM、RequestRate、状态码分布、网络错误数。 > 用于观察真实负载与错误率。 > - **质量类**(仅成功样本,`2xx 且无错误`):延迟(Avg/Min/Max/P50-99)、TTFT、TPOT、ITL、 > 输入/输出 token(总量与平均)、吞吐、TPM、QPS。失败/超时请求不会拖偏这些数值, > 避免超时的巨大延迟污染均值、失败请求的 0 token 拉低平均。 - **QPS** = 成功请求数 / 总耗时 - **RPM (Requests Per Minute)** = 全部请求数 / 总耗时 × 60(含失败请求) - **TPM (Tokens Per Minute)** = 成功请求 (输入 + 输出 tokens) / 总耗时 × 60(有效吞吐口径) - **TTFT (Time To First Token)**:从请求发送到收到第一个 token 的耗时(流式有效,仅成功) - **TPOT (Time Per Output Token)**:成功请求 (总耗时 - TTFT) / (输出 token 数 - 1) 的平均 - **输出吞吐**:成功输出总 tokens / 总耗时 ## 目录结构 ``` bench/ ├── cmd/bench/main.go # CLI 入口 ├── internal/ │ ├── config/ # 配置管理 │ ├── metrics/ # 指标采集与聚合 │ ├── prompt/ # 1k/8k 上下文构造 │ ├── provider/ # 多协议适配层 │ ├── runner/ # 并发调度核心 │ └── report/ # HTML 报告生成 ├── test/ # 单元测试 ├── docs/ # 设计文档 └── README.md ``` ## 开发 ```bash # 编译 go build ./... # 运行测试 go test -short -race -timeout=60s ./test/... # 代码检查 go vet ./... gofmt -l . ``` ## 注意事项 - 1k / 8k 上下文长度按 `1 token ≈ 4 字符` 估算构造,实际 token 数取决于模型 tokenizer - 不同云厂商的 SSE 事件格式可能存在细微差异,本工具已兼容主流实现 - 请确保已获得目标 API 的使用授权,避免触发限流 - 压测结果受本地网络与机器性能影响,建议在相同环境下进行多次测试对比 ## Token 消耗与压测成本 > **核心结论**:本工具**本地构造 Prompt 不消耗任何 token**(只是字符串拼接),但**将 Prompt 发送给目标 LLM 服务时会按真实 token 数计费**,费用由模型提供方收取。 ### 1. 本地 Prompt 构造(零成本) `prompt.Build()` 仅在内存中拼接内置的中英文文本片段(`internal/prompt/prompt.go:11-17`), 不调用任何外部 API,**零网络请求、零费用**。 ### 2. 发送给 LLM 时(真实计费) 每次请求会消耗两类 token: | 消耗项 | 说明 | 配置参数 | |--------|------|----------| | **输入 token** | 完整 Prompt 内容(按模型 tokenizer 真实计算) | `--context-length`、`--chars-per-token` | | **输出 token** | LLM 生成的内容(受 max_tokens 上限限制) | `--max-output-tokens` | ### 3. 字符/token 估算的偏差 `--chars-per-token`(默认 4)是**估算值**,与模型真实 tokenizer 存在偏差: | 内容类型 | 经验值 | 与默认值的偏差 | |----------|--------|----------------| | 纯英文 | 1 token ≈ 4 字符 | 准确 | | 中英文混合 | 1 token ≈ 2~3 字符 | 估算**偏少**,实际 prompt 比预期长 1~2 倍 | | 代码片段 | 1 token ≈ 3~5 字符 | 接近 | | 中文为主 | 1 token ≈ 1~2 字符 | 估算**严重偏少** | **压测中文场景时建议显式设置 `--chars-per-token 2`**,否则实际请求量会是预期 1~2 倍。 ### 4. 压测成本估算(示例) 以 1k context、256 max output、OpenAI GPT-4 级别定价为例: ``` 单次成本 ≈ input_tokens × input_price + output_tokens × output_price ≈ 1100 × $0.15/M + 256 × $0.60/M ≈ $0.0003 / 请求 100 请求 ≈ $0.03 1000 请求 ≈ $0.30 10000 请求 ≈ $3.00 ``` **8k context + 高并发(50)+ 大输出(1024)** 的成本可能是上述示例的 **50~100 倍**,压测前务必先小规模验证。 ### 5. 节省成本的最佳实践 1. **先小规模验证**:`--requests 10 --concurrency 2` 观察真实 token 消耗与延迟 2. **使用 `--max-output-tokens`** 限制单次输出(避免模型"啰嗦"撑爆账单) 3. **压测中文模型时设置 `--chars-per-token 2`**(否则 prompt 会被拉长) 4. **利用 `--warmup`** 预热连接,避免冷启动造成的重试成本 5. **复用 `--timeout`**:超时请求仍会计费(部分服务商),合理设置避免无效重试 6. **小并发起步**:先用 `--concurrency 2` 跑通,再逐步加到目标值 7. **优先在低峰期压测**:部分云厂商有阶梯定价 8. **关注 429/5xx**:触发限流后部分服务商会**仍按输入 token 计费**,失败请求也是钱