diff --git a/bench/README.md b/bench/README.md index 1718ae85..a196fd86 100644 --- a/bench/README.md +++ b/bench/README.md @@ -1,262 +1,63 @@ -# bench +# Benchmark Manual -轻量、零依赖、自包含的测量工装:一套入口、两种测量模式、一份数据契约。它**只在仓库里跑** —— 不随 -npm 包发布、不参与构建、不进 `verify:release` / `release.yml`,也从不成为默认门禁(设计议题 #205)。 +## 测试前的准备 -本套收编了两份来源:**#103(@OldLigant)** 的无模型工具级确定性对比,和 **#183(@Richeir)** 的模型 -驱动 token A/B 骨架;口径对齐 **#173** 的意图 —— 本地性能测量只作人类证据,不进 CI。归属记录见 -`docs/CHANGELOG.md`。 +```console +npm ci --ignore-scripts +npm run build && npm run build:ptc # lib/ + 原生 worker +``` -## 三种入口形态 +### 再准备 dsh + 两个 profile -| 形态 | 命令 | 用途 | -| --- | --- | --- | -| 唯一跨平台入口 | `node bench/cli.mjs ` | 全部子命令: `setup \| ab \| report \| panel \| tools` | -| npm scripts | `npm run bench:ab` 等 | 日常使用;装包后 `bench/` 不存在,脚本给出明确报错而不是 ENOENT | -| 底层单脚本 | `node bench/setup.mjs` / `bench/compare.mjs` / `bench/report/panel.mjs` / `bench/tools/index.mjs` | 排障与单步重跑;`ab` 只是按顺序把 runner 与 compare 连起来 | +```console +npm i -g @deepseek-ai/dsh +node bench/cli.mjs setup # 会自动添加 2 个 headless profile(base、dsh),然后在 dsh 上面安装本地代码 build 出来的插件 +``` -`node bench/cli.mjs --help` 列出全部子命令,` --help` 给出该命令自己的旗标。任何路径都从 -模块位置解析(在哪个工作目录下调用都一样),且**不经过 shell** —— `.cmd`/`.ps1` shim 无法被 Node 管道, -而 shell 会重新解释每个参数。 +### 配 LLM 链接 -## 退出码 +跑 `node bench/cli.mjs setup` 会在自动创建;也可以手动创建(会覆盖已有的): -| 码 | 含义 | -| --- | --- | -| `0` | 正常;或一个**弃权**的成本门(噪声底未知 ⇒ 不读方向) | -| `1` | 失败:`report` 的判决门失败 · `tools` 出现被接受状态之外的项 · `setup` 的某个检查或步骤失败 | -| `2` | 用法/输入错误:未知命令、未知旗标、缺值、越界参数;装包后缺 `bench/` 也走这里 | -| `3` | **已声明但尚未接线**:目标文件不存在,或存在但没导出 `main(argv, io)` —— 打印所属切片与目标路径,绝不静默成功 | +```console +cp bench/llm.patch.example.yml bench/llm.patch.yml # bash +``` -`report`(以及 `ab` 的最后一步)的退出码**就是裁决**:`1` 表示门禁失败;一个没能跑起来的成本门 -(噪声底未知等)弃权并退 `0`。 +填 `bench/llm.patch.yml`: -## 两种测量模式(以及各自不该断言什么) +- `baseURL`:服务器地址,如 `https://host/v1` +- `` :模型 id,两处必须一致 +- `apiKeyEnv`:放密钥的环境变量名,如 `BENCH_SERVER_API_KEY` +- `contextWindow` / `maxTokens`:模型真实窗口 / 单次输出上限(默认最大 256K 上下文,单次最大 32K 输出) -| 模式 | 入口 | 用模型 | 断言什么 | 不该断言什么 | -| --- | --- | --- | --- | --- | -| 成本 A/B | `bench ab` → `bench report` | 是 | **通过率先行**(任一任务 ✅→❌ 就是回归);只有**两侧都通过**的配对参与成本统计;方向必须超过 A/A 噪声底 | 时间;落在噪声带内的 Δ;没有噪声底时的任何方向 | -| 工具级确定性对比 | `bench tools` | 否 | 只有 `MATCH / EXPECTED_DIFFERENCE / EXPECTED_REJECTION` 算通过,其余状态(含 `MISMATCH` / `UNSTABLE`)都退 `1` | 时间;同二进制两侧的"提速";任何性能结论 | -| 本地性能(#173 的意图) | 各命令打印的 median | 否 | **什么都不裁决** —— median 只作人类证据 | 进 CI、进门禁、进任何性能回归判定 | +密钥只放在 `~/.dsh/.credentials.yaml` 的 `ref` 节点下面的环境变量里面,这样避免出现在项目源文件、对话的上下文 -三者共同的一条:**永不裁决时间**。 +## 怎么跑全量 -### 成本 A/B 的口径 +```console +node bench/cli.mjs ab +``` -1. **通过率先行**:`--fail-on both`(默认)先判通过率,任一任务从 ✅ 变 ❌ 即失败;`--fail-on pass-drop` - 只判通过率,`regression` 只判 token,`none` 只出报告; -2. **配对**:按 `task × repeat` 配对;`--regression-scope task|total|both` 决定 token 回归判每个任务、 - 判总量还是两者都判;`--tolerance-pct N`(默认 `2`)是回归容忍带; -3. **只有两侧都通过的配对参与成本统计** —— 一侧失败的配对连 token 一起被排除,并在报告里单列; -4. **A/A 噪声底**:没有噪声底时,所有成本方向弃权(`noise-floor-unknown`),报告只给测到的 Δ。 +## 怎么跑单个 task -### 工具级确定性对比的口径 +```console +node bench/cli.mjs ab --task csv-sum +``` -- `tools/measure.mjs` 的 `compare()` **判断顺序本身就是语义**:`TIMEOUT → ERROR → UNSTABLE → - (!sameAsBaseline ? (expectedDifference ? EXPECTED_DIFFERENCE : MISMATCH)) → expectedError 命中 ? - EXPECTED_REJECTION → (!eligible ? WORKER_ERROR) → MATCH`。其中"两侧返回同一个 error 信封"若不在 - 任务里显式声明为 `expectedError`,必须落 `WORKER_ERROR`,**不能**当成 `MATCH`(否则一次崩溃会被报告成提速); -- `speedup` 只在 `MATCH` 且 candidate median > 0 时才有意义;任一轮超时或不稳定,整项不给方向; -- 夹具在**计时之外**用 `mkdtemp` 生成,跑完删除;删除**之前**先整树盘点,遇到不是自己创建的内容就报错并保留现场; -- 报告**独占创建**(`flag: 'wx'`):重跑不覆盖上一次的证据,而是退 `1` 并提示 `Refusing to overwrite`; -- 每次调用都显式带 `outputMode`(native 默认是 content):省略参数两侧就不可比。 +## 结果在哪看 -## 子命令 +- 在 bench/runs 文件夹里面可以看到 +- 文件夹名称格式为 `benchmark-YYYY-MM-DD-NN`(按当天日期编号,从 `-00` 起) +- 跑完自动在组目录写 `DSH-OPS Benchmark Report .md` -| 子命令 | 目标 | 做什么 | -| --- | --- | --- | -| `setup` | `bench/setup.mjs` | 自检 + 幂等准备两侧 profile(`bench-base` / `bench-ops`),打印跑 sweep 的那一条命令 | -| `ab` | `bench/run.mjs` → `bench/compare.mjs` | 先基线、再候选、再比较,三步共用**一个** `bench/runs/benchmark-test-NN` 组目录 | -| `report` | `bench/compare.mjs` | 从已封存的 `run.json` 渲染报告(`DSH-OPS Benchmark Report .md`) | -| `panel` | `bench/report/panel.mjs` | 生成/打开一个只读 `run.json` 的 `panel.html` | -| `tools` | `bench/tools/index.mjs` | 无模型的工具级确定性对比 | +## 任务一览 -### `setup` +六个任务各自压一种能力,`ops_*` 工具名来自 `publicToolName()`。每个任务的期望值都由夹具生成器现算(`derive*`),判卷在 `bench/lib/verify.mjs`。 -`--base-profile` / `--cand-profile` / `--template`(默认 `headless`),`--pin-shell ` / `--no-pin`, -`--permission-mode ` / `--no-permission-mode`(`danger-full-access`(默认)/ `workspace-write` / -`read-only` / `none`),`--plugin `(`local` 默认装本 checkout 的本地构建 / npm spec / `none`), -`--reinstall`,`--dsh-home `,`--dsh `,`--force`,`--dry-run`,`--no-build-check`。 - -顺序是故意的:先跑 preflight(权限组合、`PSModulePath`、本地构建),**检查失败就在碰 profile 之前拒绝**; -一套配置一旦会被写成"量的是权限模式"或"跑的是旧构建",改 profile 也修不好,只会在下一次运行里让报错更难读。 -`--dry-run` 打印每一步写入与命令,什么也不改。 - -`setup` 会把 `bench/llm.patch.example.yml` 复制成 `bench/llm.patch.yml`(仅当后者不存在),之后的每次 dsh -调用都以 `--patch` 带上它;把服务器、模型 id 与 `apiKeyEnv` 填进副本即可,**密钥只放环境变量或 -`~/.dsh/.credentials.yaml`,绝不写进这个文件**。副本是操作员的,存在就一字不改。 - -### `ab` - -`--task `(可重复)、`--repeat `、`--folder `、`--base