{"version":3,"file":"benchmark.d.ts","sourceRoot":"","sources":["../../../src/core/shared-inference/benchmark.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,WAAW,kCAAkC;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iCAAiC;IACjD,OAAO,EAAE;QACR,OAAO,EAAE,WAAW,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,MAAM,EAAE,MAAM,CAAC;KACf,CAAC;IACF,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACvD,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAClD,4BAA4B,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,0BAA0B,EAAE,MAAM,CAAC;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;CACpB;AAcD;;;;GAIG;AACH,wBAAsB,8BAA8B,CACnD,OAAO,EAAE,kCAAkC,GACzC,OAAO,CAAC,iCAAiC,CAAC,CA6F5C","sourcesContent":["/**\n * Inference scheduling benchmark harness (3.0.0 foundation).\n *\n * Synthetic-backend benchmark over the real SharedInferenceScheduler. It\n * measures scheduling behavior (queue latency, TTFT, busy-slot utilization,\n * avoidable idle) independently of real Qwen. Real-Qwen measurements are a\n * separate QA artifact; synthetic capacity profiles are labeled as such.\n */\n\nimport { InMemoryInferenceQueueStore } from \"./in-memory-inference-queue-store.js\";\nimport { SharedInferenceScheduler } from \"./scheduler.js\";\nimport type { SharedInferenceResource } from \"./types.js\";\n\nexport interface InferenceSchedulerBenchmarkOptions {\n\tcapacity: number;\n\tagentCount: number;\n\trequestsPerAgent: number;\n\t/** Synthetic generation wall time per request. */\n\tgenerationMs: number;\n\t/** Synthetic tool time between an agent's sequential requests. */\n\ttoolMs?: number;\n\t/** Deterministic queue-wait deadline (0 = unbounded). */\n\tqueueWaitTimeoutMs?: number;\n}\n\nexport interface InferenceSchedulerBenchmarkResult {\n\tprofile: {\n\t\tbackend: \"synthetic\";\n\t\tcapacity: number;\n\t\tagentCount: number;\n\t\trequestsPerAgent: number;\n\t\tgenerationMs: number;\n\t\ttoolMs: number;\n\t};\n\trequestCount: number;\n\tqueueWaitMs: { p50: number; p95: number; max: number };\n\tttftMs: { p50: number; p95: number; max: number };\n\tperRequestDecodeTokensPerSec: { p50: number; p95: number };\n\taggregateModelTokensPerSec: number;\n\tbusySlotPercent: number;\n\tavoidableIdleMs: number;\n\ttotalWallMs: number;\n\tcompletedCount: number;\n\tfailedCount: number;\n}\n\nconst SYNTHETIC_OUTPUT_TOKENS = 256;\n\nfunction percentile(sorted: number[], p: number): number {\n\tif (sorted.length === 0) return 0;\n\tconst index = Math.min(sorted.length - 1, Math.ceil((p / 100) * sorted.length) - 1);\n\treturn sorted[index] ?? 0;\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Run a synthetic concurrency-profile benchmark. Each logical agent issues\n * `requestsPerAgent` sequential generations through the scheduler (capacity\n * `capacity`). All agents run concurrently, so queueing emerges naturally.\n */\nexport async function runInferenceSchedulerBenchmark(\n\toptions: InferenceSchedulerBenchmarkOptions,\n): Promise<InferenceSchedulerBenchmarkResult> {\n\tconst store = new InMemoryInferenceQueueStore();\n\tconst scheduler = new SharedInferenceScheduler({\n\t\tstore,\n\t\twaitPollMs: 1,\n\t\tqueueWaitTimeoutMs: options.queueWaitTimeoutMs ?? 0,\n\t});\n\tconst resource: SharedInferenceResource = {\n\t\tresourceId: \"synthetic\",\n\t\tbackend: \"synthetic\",\n\t\tmodel: \"synthetic\",\n\t\tlocation: \"local\",\n\t\tcapacity: options.capacity,\n\t\tstate: \"available\",\n\t};\n\tawait scheduler.registerResource(resource);\n\n\tconst queueWaitSamples: number[] = [];\n\tconst ttftSamples: number[] = [];\n\tlet totalOutputTokens = 0;\n\tlet failedCount = 0;\n\tconst start = Date.now();\n\n\tconst agent = async (agentIndex: number): Promise<void> => {\n\t\tfor (let i = 0; i < options.requestsPerAgent; i++) {\n\t\t\tconst requestedAt = Date.now();\n\t\t\tconst acquired = await scheduler.acquire({\n\t\t\t\tlogicalAgentId: `agent_${agentIndex}`,\n\t\t\t\tresource,\n\t\t\t\tmodel: { provider: \"synthetic\", id: \"synthetic\" },\n\t\t\t\tpriority: { base: 0 },\n\t\t\t});\n\t\t\tif (acquired.status !== \"admitted\") {\n\t\t\t\tfailedCount += 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst admittedAt = Date.now();\n\t\t\tqueueWaitSamples.push(admittedAt - requestedAt);\n\t\t\tttftSamples.push(1); // synthetic backend: first token after 1ms\n\t\t\tawait sleep(options.generationMs);\n\t\t\tawait scheduler.release(acquired.admitted, {\n\t\t\t\tstate: \"COMPLETED\",\n\t\t\t\tusage: { input: 1000, output: SYNTHETIC_OUTPUT_TOKENS },\n\t\t\t});\n\t\t\ttotalOutputTokens += SYNTHETIC_OUTPUT_TOKENS;\n\t\t\tif (options.toolMs && options.toolMs > 0) await sleep(options.toolMs);\n\t\t}\n\t};\n\n\tawait Promise.all(Array.from({ length: options.agentCount }, (_, i) => agent(i)));\n\n\tconst totalWallMs = Date.now() - start;\n\tconst status = await scheduler.status();\n\tconst busyPercent =\n\t\ttotalWallMs > 0\n\t\t\t? (status.aggregate.busySlots * options.generationMs * options.agentCount * options.requestsPerAgent) /\n\t\t\t\ttotalWallMs /\n\t\t\t\t100\n\t\t\t: 0;\n\n\tqueueWaitSamples.sort((a, b) => a - b);\n\tttftSamples.sort((a, b) => a - b);\n\tconst perRequestDecode = options.generationMs > 0 ? SYNTHETIC_OUTPUT_TOKENS / (options.generationMs / 1000) : 0;\n\tconst aggregateTokensPerSec = totalWallMs > 0 ? (totalOutputTokens / totalWallMs) * 1000 : 0;\n\n\treturn {\n\t\tprofile: {\n\t\t\tbackend: \"synthetic\",\n\t\t\tcapacity: options.capacity,\n\t\t\tagentCount: options.agentCount,\n\t\t\trequestsPerAgent: options.requestsPerAgent,\n\t\t\tgenerationMs: options.generationMs,\n\t\t\ttoolMs: options.toolMs ?? 0,\n\t\t},\n\t\trequestCount: options.agentCount * options.requestsPerAgent,\n\t\tqueueWaitMs: {\n\t\t\tp50: percentile(queueWaitSamples, 50),\n\t\t\tp95: percentile(queueWaitSamples, 95),\n\t\t\tmax: queueWaitSamples.length > 0 ? queueWaitSamples[queueWaitSamples.length - 1]! : 0,\n\t\t},\n\t\tttftMs: {\n\t\t\tp50: percentile(ttftSamples, 50),\n\t\t\tp95: percentile(ttftSamples, 95),\n\t\t\tmax: ttftSamples.length > 0 ? ttftSamples[ttftSamples.length - 1]! : 0,\n\t\t},\n\t\tperRequestDecodeTokensPerSec: { p50: perRequestDecode, p95: perRequestDecode },\n\t\taggregateModelTokensPerSec: aggregateTokensPerSec,\n\t\tbusySlotPercent: busyPercent,\n\t\tavoidableIdleMs: status.aggregate.avoidableIdleMs,\n\t\ttotalWallMs,\n\t\tcompletedCount: options.agentCount * options.requestsPerAgent - failedCount,\n\t\tfailedCount,\n\t};\n}\n"]}