char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

parabelboiui: add read_media tool (#25877)4dd127584

char/aliceai-t5-moe
7.7 KiB243 linesraw
1// Microbenchmarks for the functions on the agentic-thread streaming hot path.
2//
3// Every function here is invoked from a `$derived` that is invalidated on each
4// streamed token, for every tool-call section in the message. These numbers give
5// the per-call cost; multiply by (tokens x sections) for the real damage.
6//
7// Run: npx vitest bench --project=unit tests/unit/agentic-hotpath.bench.ts
8
9import { classifyToolResult, parseToolResultWithMedia } from '$lib/utils/agentic';
10import { detectIncompleteCodeBlock, highlightCode } from '$lib/utils/code';
11import { computeLineDiff } from '$lib/utils/compute-line-diff';
12import { preprocessLaTeX } from '$lib/utils/latex-protection';
13import { parsePartialJsonArgs } from '$lib/utils/parse-partial-json-args';
14import { extractSearchQuery, extractSearchResults } from '$lib/utils/search-results';
15import { all as lowlightAll } from 'lowlight';
16import rehypeHighlight from 'rehype-highlight';
17import rehypeKatex from 'rehype-katex';
18import rehypeStringify from 'rehype-stringify';
19import { remark } from 'remark';
20import remarkBreaks from 'remark-breaks';
21import remarkGfm from 'remark-gfm';
22import remarkMath from 'remark-math';
23import remarkRehype from 'remark-rehype';
24import { bench, describe } from 'vitest';
25
26// --- fixtures -------------------------------------------------------------
27
28function lines(n: number, seed: string): string {
29	const out: string[] = [];
30
31	for (let i = 0; i < n; i++) out.push(`${seed} line ${i} const value_${i} = compute(${i});`);
32
33	return out.join('\n');
34}
35
36const SHELL_OUTPUT_1KB = lines(12, 'out');
37const SHELL_OUTPUT_200KB = lines(2600, 'out');
38const SHELL_OUTPUT_2MB = lines(26000, 'out');
39// Realistic exec_shell_command result: the exit-code marker is the final line,
40// which is what the un-anchored EXIT_CODE regex has to scan the whole blob for.
41const SHELL_2MB_WITH_EXIT = `${SHELL_OUTPUT_2MB}\n[exit code: 0]`;
42const EDIT_OLD_400 = lines(400, 'old');
43const EDIT_NEW_400 = lines(400, 'new');
44const EDIT_OLD_50 = lines(50, 'old');
45const EDIT_NEW_50 = lines(50, 'new');
46const WRITE_FILE_ARGS = JSON.stringify({
47	content: lines(1500, 'src'),
48	path: '/src/lib/thing.ts'
49});
50const MARKDOWN_50KB = Array.from(
51	{ length: 400 },
52	(_, i) =>
53		`## Section ${i}\n\nSome **bold** prose with a [link](https://example.com) and \`inline\` code.\n\n- bullet one\n- bullet two\n\n\`\`\`ts\nconst x${i} = ${i};\n\`\`\`\n`
54).join('\n');
55const CODE_BLOCK_5KB = lines(60, 'code');
56
57// --- A: computeLineDiff (O(m*n) LCS, allocates a full matrix) --------------
58
59describe('computeLineDiff', () => {
60	bench('50 x 50 lines', () => {
61		computeLineDiff(EDIT_OLD_50, EDIT_NEW_50);
62	});
63
64	bench('400 x 400 lines', () => {
65		computeLineDiff(EDIT_OLD_400, EDIT_NEW_400);
66	});
67});
68
69// --- B: the unified processor, rebuilt per call ---------------------------
70// Mirrors MarkdownContent.svelte:144-176. The local rehype/remark plugins are
71// omitted (they pull in browser-only modules); rehypeHighlight + lowlightAll is
72// the dominant term, so this is a lower bound on the real cost.
73
74function buildProcessor() {
75	// eslint-disable-next-line @typescript-eslint/no-explicit-any
76	let proc: any = remark().use(remarkGfm);
77
78	proc = proc.use(remarkMath).use(remarkBreaks).use(remarkRehype).use(rehypeKatex);
79
80	return proc.use(rehypeHighlight, { languages: lowlightAll }).use(rehypeStringify, {
81		allowDangerousHtml: true
82	});
83}
84
85describe('markdown processor', () => {
86	bench('build processor (per processMarkdown call, x2)', () => {
87		buildProcessor();
88	});
89
90	const prebuilt = buildProcessor();
91
92	bench('parse 50KB markdown with a prebuilt processor', () => {
93		prebuilt.parse(MARKDOWN_50KB);
94	});
95});
96
97// Isolates the O(n^2) term measured in Tier 1: processMarkdown re-parses the
98// WHOLE accumulated string every frame, while only the last block can have
99// changed. If parse() dominates at these sizes, incremental parsing (re-parsing
100// only the tail after the last stable block) is the fix; if not, the cost is
101// downstream in transform/stringify/DOM.
102describe('markdown parse scaling (whole-string reparse per frame)', () => {
103	const proc = buildProcessor();
104	const prose = (kb: number) =>
105		Array.from(
106			{ length: Math.ceil((kb * 1024) / 64) },
107			(_, i) => `The quick brown fox jumps over the lazy dog. Sentence ${i}.`
108		).join(' ');
109	const MD_3KB = prose(3);
110	const MD_11KB = prose(11);
111	const MD_26KB = prose(26);
112
113	bench('parse 3KB', () => {
114		proc.parse(MD_3KB);
115	});
116
117	bench('parse 11KB', () => {
118		proc.parse(MD_11KB);
119	});
120
121	bench('parse 26KB', () => {
122		proc.parse(MD_26KB);
123	});
124
125	bench('parse only a 200-char tail (proposed incremental)', () => {
126		proc.parse(MD_26KB.slice(-200));
127	});
128});
129
130// processMarkdown runs these over the WHOLE accumulated string every frame too,
131// before parse() even starts. Measure them before assuming parse is the target.
132describe('other whole-string passes per frame', () => {
133	const prose = (kb: number) =>
134		Array.from(
135			{ length: Math.ceil((kb * 1024) / 64) },
136			(_, i) => `The quick brown fox jumps over the lazy dog. Sentence ${i}.`
137		).join(' ');
138	const MD_3KB = prose(3);
139	const MD_11KB = prose(11);
140	const MD_26KB = prose(26);
141	const MD_26KB_LATEX = `${MD_26KB} and some math $x^2 + y^2 = z^2$ inline.`;
142
143	bench('preprocessLaTeX 3KB (no latex present)', () => {
144		preprocessLaTeX(MD_3KB);
145	});
146
147	bench('preprocessLaTeX 11KB (no latex present)', () => {
148		preprocessLaTeX(MD_11KB);
149	});
150
151	bench('preprocessLaTeX 26KB (no latex present)', () => {
152		preprocessLaTeX(MD_26KB);
153	});
154
155	bench('preprocessLaTeX 26KB (latex present)', () => {
156		preprocessLaTeX(MD_26KB_LATEX);
157	});
158
159	bench('detectIncompleteCodeBlock 26KB', () => {
160		detectIncompleteCodeBlock(MD_26KB);
161	});
162});
163
164// --- C: extractSearchResults, run for EVERY tool of every type ------------
165
166describe('extractSearchResults (only .length > 0 is consumed)', () => {
167	bench('1KB non-search tool result', () => {
168		extractSearchResults(SHELL_OUTPUT_1KB);
169	});
170
171	bench('200KB non-search tool result', () => {
172		extractSearchResults(SHELL_OUTPUT_200KB);
173	});
174
175	bench('2MB non-search tool result', () => {
176		extractSearchResults(SHELL_OUTPUT_2MB);
177	});
178});
179
180describe('extractSearchQuery', () => {
181	bench('write_file args (throws on the JSON.parse path)', () => {
182		extractSearchQuery(WRITE_FILE_ARGS);
183	});
184});
185
186// --- E: the un-anchored exit-code regex -----------------------------------
187// Reproduced inline so the bench is independent of the current call site.
188
189const EXIT_CODE_TAIL = /\[exit code: (-?\d+)\]\s*$/;
190
191describe('exit-code regex', () => {
192	bench('whole 2MB blob (current behaviour)', () => {
193		SHELL_2MB_WITH_EXIT.match(EXIT_CODE_TAIL);
194	});
195
196	bench('last 64 chars only (proposed)', () => {
197		SHELL_2MB_WITH_EXIT.slice(-64).match(EXIT_CODE_TAIL);
198	});
199});
200
201// --- per-line result parsers ----------------------------------------------
202
203describe('parseToolResultWithMedia', () => {
204	bench('1KB', () => {
205		parseToolResultWithMedia(SHELL_OUTPUT_1KB, []);
206	});
207
208	bench('200KB', () => {
209		parseToolResultWithMedia(SHELL_OUTPUT_200KB, []);
210	});
211
212	bench('2MB', () => {
213		parseToolResultWithMedia(SHELL_OUTPUT_2MB, []);
214	});
215});
216
217describe('classifyToolResult', () => {
218	bench('200KB plain text (falls through to looksLikeMarkdown)', () => {
219		classifyToolResult(SHELL_OUTPUT_200KB);
220	});
221});
222
223describe('parsePartialJsonArgs', () => {
224	bench('write_file args, ~60KB (char-by-char scan)', () => {
225		parsePartialJsonArgs(WRITE_FILE_ARGS);
226	});
227});
228
229// --- F / I: highlight.js ---------------------------------------------------
230
231describe('highlightCode', () => {
232	bench('short bash command (title row, per token)', () => {
233		highlightCode('grep -rn "foo" src/ | head -50', 'bash');
234	});
235
236	bench('5KB known language', () => {
237		highlightCode(CODE_BLOCK_5KB, 'typescript');
238	});
239
240	bench('5KB unknown language -> highlightAuto', () => {
241		highlightCode(CODE_BLOCK_5KB, 'not-a-language');
242	});
243});