소스 검색

test(perf): calibrate catalog for standard hosted CI

Tianyi Cui 2 주 전
부모
커밋
daa7f60630

+ 2 - 2
.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.md
-2026-09-06-backend-continuation-performance.md: 0ad7f5267136181e5d45d4bc6f452f6cf9a744ca
-2026-09-06-backend-continuation-performance.zh.md: e07cd154d1d879cca010fb0e36872205710c37a4
+2026-09-06-backend-continuation-performance.md: c247c7b9c4e2612603e0fb04b4f1a03b80314407
+2026-09-06-backend-continuation-performance.zh.md: bba1b4fc8a742ee261be6d09f61dd754568ebe0b

+ 4 - 2
.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.md

@@ -31,13 +31,13 @@ The parent bounds every child to 60 seconds, checks timeout, signal, exit, and r
 
 The implementation reference is `925e012340f033f0521e802ba8569ce6dd7ef1ac` on Apple M4 Pro, macOS arm64, Node 24.19.0. Two exclusive five-sample runs use the same seed and no product optimization. Durations below are milliseconds; source expectations round above the observed run medians rather than imposing an unimplemented optimization target.
 
-| Case | Run 1 raw totals | Run 2 raw totals | Medians | Reference expectation | CI budget |
+| Case | Run 1 raw totals | Run 2 raw totals | Medians | Historical M4 expectation | Historical scaled budget |
 |---|---|---|---|---:|---:|
 | Request history | 209.134, 210.333, 208.959, 236.355, 238.685 | 222.833, 213.911, 208.089, 211.494, 209.137 | 210.333 / 211.494 | 220 | 550 |
 | Tool continuation | 358.953, 324.790, 318.861, 320.119, 322.896 | 324.280, 321.952, 340.409, 325.470, 324.312 | 322.896 / 324.312 | 340 | 850 |
 | Child catalog | 318.730, 309.006, 311.404, 308.565, 310.105 | 308.670, 310.030, 280.086, 303.084, 284.829 | 310.105 / 303.084 | 320 | 800 |
 
-Continuation retains approximately 22.295 MiB; its source expectation is 23 MiB and its budget is 28.75 MiB. Time expectations use the existing [calibration helper](../../../../benchmarks/support/calibration.ts): 2× shared CI time scale and 1.25× variance headroom. Memory uses only 1.25× headroom. The scale is inherited from the existing lane's calibration, not a new Linux measurement of these cases; CI evidence remains necessary when runner characteristics change. Baseline budgets protect the measured implementation; tighter budgets belong with a measured behavior-preserving fix.
+Continuation retains approximately 22.295 MiB; its source expectation is 23 MiB and its budget is 28.75 MiB. Request-history, tool-continuation, and SDK time expectations use the existing [calibration helper](../../../../benchmarks/support/calibration.ts): 2× shared CI time scale and 1.25× variance headroom. Memory uses only 1.25× headroom. The scale is inherited from the existing lane's calibration, not a new Linux measurement of these cases; CI evidence remains necessary when runner characteristics change. Baseline budgets protect the measured implementation; tighter budgets belong with a measured behavior-preserving fix.
 
 A separate plain-Node request-history CPU profile attributes 132.876 ms of sampled self time to deepFreeze called by buildRequest during a 211.300 ms operation. This identifies repeated traversal of already-frozen history as a focused investigation target, not a proven optimization result. Catalog first/repeat timings remain separate because a second listing still reads body-bearing seeded children after observations are released.
 
@@ -45,6 +45,8 @@ The shipped SDK variant completes 100 turns, 200 requests, and 800 real file rea
 
 The first Linux x64 CI measurement at commit `1dc3296eba631d51fbb3bb50e249bf3cc0fce9f6` ran on `VM-7-113-ubuntu-ci-10` with Node 24.18.1 ([run 34017868081, attempt 1, job 101444810498](https://github.com/deepseek-harness/deepseek-harness/actions/runs/34017868081/job/101444810498)). The SDK median was 2,753.441 ms against its 4,250 ms budget, and tool-continuation retained-heap median was 22.274 MiB against 28.75 MiB. Request-history and tool-continuation time budgets failed: 785.498 ms against 550 ms and 1,077.285 ms against 850 ms, respectively. The unchanged Session-reopen open phase also failed at 31.6 ms against 30 ms. [Attempt 2, job 101447076381](https://github.com/deepseek-harness/deepseek-harness/actions/runs/34017868081/job/101447076381) passed every benchmark on the same commit and unchanged budgets, but used `VM-7-113-ubuntu-ci-29` with Node 24.19.0. The gate runner suppressed successful child output, so that attempt supplies a passing verdict rather than raw medians. The changed runner and Node version prevent attributing the difference solely to contention or claiming stable repeated CI calibration; neither the budgets nor the shared scale are changed on this evidence.
 
+Catalog uses an explicit 900 ms expected CI duration and only the existing 1.25× headroom, yielding 1,125 ms without applying the reference-machine scale again. The standard two-CPU hosted `ubuntu-24.04` [run 34033336380, job 101487280801](https://github.com/deepseek-harness/deepseek-harness/actions/runs/34033336380/job/101487280801) reports five unchanged-catalog totals of 797.374, 883.157, 858.364, 790.569, and 904.579 ms: median 858.364 ms exceeds the historical 800 ms budget. The 320 ms M4 expectation above remains historical evidence, not a CI measurement. This follows the explicit-CI calibration used by Session reopening (50 ms expected CI); no shared factor, other scenario budget, workload, timing endpoint, or product implementation changes. Deterministic controls use the same assertion as the measured verdict: the unrounded recorded median passes 1,125 ms and fails 800 ms, while a synthetic 1,400 ms median fails 1,125 ms. A passing run on a faster host does not calibrate the standard hosted runner.
+
 ## Alternatives considered
 
 **Repeat existing migration and first-open variants.** Rejected: those twelve cases already distinguish read-only preparation from writable publication. These cases use the current generation and begin or continue actual model work, or enumerate a corpus rather than open one Session.

+ 4 - 2
.agents/notes/implemented/testing/2026-09-06-backend-continuation-performance.zh.md

@@ -31,13 +31,13 @@ Status: implemented
 
 实现参考为 Apple M4 Pro、macOS arm64、Node 24.19.0 上的 `925e012340f033f0521e802ba8569ce6dd7ef1ac`。两轮独占的五样本运行使用相同播种数据,没有产品优化。下表时间单位为毫秒;源码期望值向上取整至实测各轮中位数以上,而不是施加尚未实现的优化目标。
 
-| 用例 | 第一轮原始总时间 | 第二轮原始总时间 | 中位数 | 参考期望 | CI 预算 |
+| 用例 | 第一轮原始总时间 | 第二轮原始总时间 | 中位数 | 历史 M4 期望 | 历史缩放预算 |
 |---|---|---|---|---:|---:|
 | 请求历史 | 209.134, 210.333, 208.959, 236.355, 238.685 | 222.833, 213.911, 208.089, 211.494, 209.137 | 210.333 / 211.494 | 220 | 550 |
 | 工具续聊 | 358.953, 324.790, 318.861, 320.119, 322.896 | 324.280, 321.952, 340.409, 325.470, 324.312 | 322.896 / 324.312 | 340 | 850 |
 | 子会话目录 | 318.730, 309.006, 311.404, 308.565, 310.105 | 308.670, 310.030, 280.086, 303.084, 284.829 | 310.105 / 303.084 | 320 | 800 |
 
-续聊保留约 22.295 MiB;源码期望值为 23 MiB,预算为 28.75 MiB。时间期望值使用现有[校准辅助函数](../../../../benchmarks/support/calibration.ts):2× 共享 CI 时间比例和 1.25× 波动余量。内存只使用 1.25× 余量。比例继承现有通道的校准,并非这些用例的新 Linux 实测值;runner 特征变化时仍需 CI 证据。基线预算保护实测实现;更紧预算属于有测量依据且保持行为的修复。
+续聊保留约 22.295 MiB;源码期望值为 23 MiB,预算为 28.75 MiB。请求历史、工具续聊及 SDK 时间期望值使用现有[校准辅助函数](../../../../benchmarks/support/calibration.ts):2× 共享 CI 时间比例和 1.25× 波动余量。内存只使用 1.25× 余量。比例继承现有通道的校准,并非这些用例的新 Linux 实测值;runner 特征变化时仍需 CI 证据。基线预算保护实测实现;更紧预算属于有测量依据且保持行为的修复。
 
 独立的纯 Node 请求历史 CPU profile 在一次 211.300 ms 操作中,将 132.876 ms 采样自身时间归因于 buildRequest 调用的 deepFreeze。这把重复遍历已冻结历史定位为聚焦调查目标,不是已证实的优化结果。目录首次/重复时间分别保留,因为观察释放后第二次列举仍读取带种子子会话的正文。
 
@@ -45,6 +45,8 @@ Status: implemented
 
 提交 `1dc3296eba631d51fbb3bb50e249bf3cc0fce9f6` 的首次 Linux x64 CI 测量使用 `VM-7-113-ubuntu-ci-10` 和 Node 24.18.1([run 34017868081,attempt 1,job 101444810498](https://github.com/deepseek-harness/deepseek-harness/actions/runs/34017868081/job/101444810498))。SDK 中位数为 2,753.441 ms,预算为 4,250 ms;工具续聊保留堆中位数为 22.274 MiB,预算为 28.75 MiB。请求历史与工具续聊时间预算失败:分别为 785.498 ms 对 550 ms、1,077.285 ms 对 850 ms。未修改的 Session 重开 open 阶段也以 31.6 ms 对 30 ms 失败。[Attempt 2,job 101447076381](https://github.com/deepseek-harness/deepseek-harness/actions/runs/34017868081/job/101447076381) 在同一提交和未修改预算下通过全部基准,但使用 `VM-7-113-ubuntu-ci-29` 和 Node 24.19.0。门禁运行器隐藏成功子进程的输出,因此该次运行只提供通过结论,不提供原始中位数。Runner 与 Node 版本同时变化,不能把差异仅归因于资源争用,也不能宣称已获得稳定的重复 CI 校准;这些证据不改变预算或共享比例。
 
+目录用例使用显式的 900 ms CI 期望时间,仅乘现有 1.25× 余量,得到 1,125 ms,不再应用参考机器比例。标准双 CPU 托管 `ubuntu-24.04` 的 [run 34033336380,job 101487280801](https://github.com/deepseek-harness/deepseek-harness/actions/runs/34033336380/job/101487280801) 报告未修改目录实现的五个总时间为 797.374、883.157、858.364、790.569 和 904.579 ms:中位数 858.364 ms 超出历史 800 ms 预算。上表 320 ms M4 期望保留为历史证据,不是 CI 测量。此方法与 Session 重开使用的显式 CI 校准一致(CI 期望为 50 ms);共享系数、其他场景预算、负载、计时终点和产品实现均不改变。确定性对照与实测判定使用同一断言:未经舍入的录制中位数通过 1,125 ms 并被 800 ms 拒绝,合成的 1,400 ms 中位数则被 1,125 ms 拒绝。更快主机上的通过结果不能校准标准托管 runner。
+
 ## 考虑过的替代方案
 
 **重复现有迁移和首次打开变体。** 拒绝:现有十二个用例已经区分只读准备与可写发布。这些用例使用当前代际并开始或继续实际模型工作,或者列举语料集合而不是打开单个 Session。

+ 2 - 2
benchmarks/agent-continuation/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write benchmarks/agent-continuation/README.md
-README.md: 13fc21ea486bea55a93011a10edaca1cbe40ff47
-README.zh.md: f3f1fdcfb95f2948add2ebec7e1ea711172b7870
+README.md: 0544489a5a5af14d74b349926eaa3f0c0bc9580d
+README.zh.md: fbe42a23502bdb72ca45b706dfdf345ee0690ca0

+ 1 - 1
benchmarks/agent-continuation/README.md

@@ -18,7 +18,7 @@ Measure long-history request processing, cold tool-heavy continuation, and repea
 
 From the repository root, build the libraries and workers with `pnpm run build:bench`, then run `pnpm exec vitest run --config vitest.bench.config.ts benchmarks/agent-continuation/agent-continuation.bench.ts`. Do not overlap timing runs with builds or other benchmarks.
 
-The test reports all five fresh-process samples and enforces reviewed median budgets. A failed worker reports its exit, signal, timeout, and stderr; temporary roots are removed even on failure. The required benchmark lane discovers this file automatically.
+The test reports all five fresh-process samples and enforces reviewed median budgets. Catalog uses a 900 ms standard hosted CI expectation with 1.25× headroom (1,125 ms); other time budgets use reference-machine scaling. A failed worker reports its exit, signal, timeout, and stderr; temporary roots are removed even on failure. The required benchmark lane discovers this file automatically.
 
 <a id="measurements"></a>
 

+ 1 - 1
benchmarks/agent-continuation/README.zh.md

@@ -18,7 +18,7 @@
 
 在仓库根目录使用 `pnpm run build:bench` 构建库和 worker,然后运行 `pnpm exec vitest run --config vitest.bench.config.ts benchmarks/agent-continuation/agent-continuation.bench.ts`。不要让计时运行与构建或其他基准重叠。
 
-测试报告全部五个新进程样本,并约束经审查的中位数预算。worker 失败时报告退出状态、信号、超时和 stderr;失败时也会删除临时根目录。必需基准通道自动发现此文件。
+测试报告全部五个新进程样本,并约束经审查的中位数预算。目录用例使用标准托管 CI 的 900 ms 期望值与 1.25× 余量(1,125 ms);其他时间预算使用参考机器缩放。worker 失败时报告退出状态、信号、超时和 stderr;失败时也会删除临时根目录。必需基准通道自动发现此文件。
 
 <a id="measurements"></a>
 

+ 29 - 4
benchmarks/agent-continuation/agent-continuation.bench.ts

@@ -14,11 +14,14 @@ import { WORKLOAD } from './workload.ts'
 const ATTEMPTS = 5
 const WORKER_TIMEOUT_MS = 60_000
 /** M4 Pro / Node 24.19 baseline expectations, before shared CI scaling and variance headroom. */
-const EXPECTED_MS = { 'request-history': 220, 'tool-continuation': 340, catalog: 320, 'profile-continuation': 1_700 } as const
+const EXPECTED_MS = { 'request-history': 220, 'tool-continuation': 340, 'profile-continuation': 1_700 } as const
+/** Standard two-CPU hosted CI catalog median is 858.364 ms; 900 ms is the rounded expectation. */
+const EXPECTED_CATALOG_CI_MS = 900
+const CATALOG_BUDGET_MS = Math.ceil(EXPECTED_CATALOG_CI_MS * PERFORMANCE_BUDGET_HEADROOM)
 const EXPECTED_RETAINED_HEAP_MB = 23
 const WORKERS = join(import.meta.dirname, '..', '.dsh-build', 'agent-continuation')
 
-type Scenario = keyof typeof EXPECTED_MS
+type Scenario = keyof typeof EXPECTED_MS | 'catalog'
 type Report = ContinuationReport | CatalogReport | ProfileReport
 
 function workerName(scenario: Scenario): string {
@@ -41,6 +44,28 @@ function median(values: readonly number[]): number {
   return [...values].sort((a, b) => a - b)[Math.floor(values.length / 2)] as number
 }
 
+function expectTotalWithinBudget(value: number, budget: number): void {
+  expect(value).toBeLessThanOrEqual(budget)
+}
+
+describe('standard hosted catalog calibration', () => {
+  it('accepts the recorded two-CPU samples that exceed the historical budget', () => {
+    const recordedMedian = median([797.373945, 883.157358, 858.363927, 790.568538, 904.5785669999999])
+
+    expect(recordedMedian).toBe(858.363927)
+    expect(() => expectTotalWithinBudget(recordedMedian, 800)).toThrow()
+    expectTotalWithinBudget(recordedMedian, CATALOG_BUDGET_MS)
+    expect(CATALOG_BUDGET_MS).toBe(1_125)
+  })
+
+  it('rejects a synthetic material catalog regression', () => {
+    const regressionMedian = median([1_380, 1_400, 1_420, 1_410, 1_390])
+
+    expect(regressionMedian).toBe(1_400)
+    expect(() => expectTotalWithinBudget(regressionMedian, CATALOG_BUDGET_MS)).toThrow()
+  })
+})
+
 describe('continuing tool-heavy Sessions with large histories', () => {
   let scratch: string | undefined
   const sources = new Map<Scenario, string>()
@@ -69,14 +94,14 @@ describe('continuing tool-heavy Sessions with large histories', () => {
         finally { await rm(root, { recursive: true, force: true }) }
       }
       const totalMs = samples.map(sample => sample.totalMs)
-      const budgetMs = ciTimeBudget(EXPECTED_MS[scenario])
+      const budgetMs = scenario === 'catalog' ? CATALOG_BUDGET_MS : ciTimeBudget(EXPECTED_MS[scenario])
       const retainedHeapBudgetMb = EXPECTED_RETAINED_HEAP_MB * PERFORMANCE_BUDGET_HEADROOM
       console.log(JSON.stringify({
         benchmark: 'agent-continuation/' + scenario, workload: WORKLOAD,
         samples, totalMs: { min: Math.min(...totalMs), median: median(totalMs), max: Math.max(...totalMs) },
         budgetMs, ...(scenario === 'tool-continuation' ? { retainedHeapBudgetMb } : {}),
       }))
-      expect(median(totalMs)).toBeLessThanOrEqual(budgetMs)
+      expectTotalWithinBudget(median(totalMs), budgetMs)
       if (scenario === 'tool-continuation') {
         expect(median((samples as ContinuationReport[]).map(sample => sample.retainedHeapMb)))
           .toBeLessThanOrEqual(retainedHeapBudgetMb)