Skip to content

Commit 8b413e2

Browse files
committed
feat(agentic): enrich benchmark point tooltips
Preserve KV offload backend versions and multinode transfer engines through ingest, then show offload type, backend/version, transfer engine, and cache-hit rates across official, comparison, and unofficial-run tooltips. 中文:丰富 Agent X 基准测试数据点的提示卡信息。在摄取流程中保留 KV 卸载后端版本和多节点传输引擎,并在正式运行、GPU 对比及非正式运行覆盖层的提示卡中展示卸载类型、后端及版本、传输引擎和 Cache 命中率。
1 parent 7cfe0be commit 8b413e2

10 files changed

Lines changed: 253 additions & 23 deletions

File tree

packages/app/cypress/e2e/gpu-compare-agentic-detail.cy.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,16 @@ const agenticBenchmarks = AGENTIC_HARDWARE.flatMap((g) =>
107107
isl: null,
108108
osl: null,
109109
conc,
110-
offload_mode: 'off',
110+
offload_mode: 'on',
111111
benchmark_type: 'agentic_traces',
112112
image: 'vllm/vllm-openai:v0.9.0',
113-
metrics: agenticMetrics(conc),
113+
metrics: {
114+
...agenticMetrics(conc),
115+
kv_offloading: 'dram',
116+
kv_offload_backend: 'mooncake',
117+
kv_offload_backend_version: '0.3.11.post1',
118+
server_gpu_cache_hit_rate: 0.875,
119+
},
114120
workers: null,
115121
date: AGENTIC_DATE,
116122
run_url: null,
@@ -176,6 +182,11 @@ describe('GPU comparison agentic point detail', () => {
176182
});
177183

178184
cy.get('[data-chart-tooltip]:visible').should('have.length', 1);
185+
cy.get('[data-chart-tooltip]:visible')
186+
.should('contain', 'Offload Type: DRAM')
187+
.and('contain', 'Offload Backend: Mooncake 0.3.11.post1')
188+
.and('contain', 'GPU Cache Hit Rate: 87.5%')
189+
.and('not.contain', 'Offload Mode');
179190
cy.get('[data-chart-tooltip]:visible [data-action="view-charts"]')
180191
.should('be.visible')
181192
.then(($link) => {

packages/app/src/components/inference/types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,17 @@ export interface AggDataEntry {
190190
isl?: number | null;
191191
/** OSL in tokens — null for agentic_traces. */
192192
osl?: number | null;
193-
// ── Agentic-only fields (populated from metrics JSONB for `agentic_traces` rows) ──
193+
// ── Runtime cache metadata (populated from metrics JSONB when emitted) ──
194194
/** "on" | "off" — whether KV cache offload to CPU was enabled. */
195195
offload_mode?: string;
196+
/** Offload tier/type, for example `dram` or `none`. */
197+
kv_offloading?: string;
198+
/** Offload implementation, for example `mooncake`, `lmcache`, or `hicache`. */
199+
kv_offload_backend?: string;
200+
/** Optional version independently declared for the offload backend. */
201+
kv_offload_backend_version?: string;
202+
/** P2P engine used to move KV state between workers on multinode runs. */
203+
kv_p2p_transfer?: string;
196204
/** Actual server-observed GPU prefix-cache hit rate (0..1). */
197205
server_gpu_cache_hit_rate?: number;
198206
/** Actual server-observed CPU prefix-cache hit rate (0..1). */

packages/app/src/components/inference/ui/GPUGraph.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,7 @@ const GPUGraph = React.memo(
853853
hardwareConfig,
854854
runUrl: d.run_url ? updateRepoUrl(d.run_url) : undefined,
855855
hasTrace: typeof d.id === 'number' ? traceAvailability?.[d.id] === true : false,
856+
locale,
856857
}),
857858
getRulerX: (d, xScale) => (xScale as d3.ScaleLinear<number, number>)(d.x),
858859
getRulerY: (d, yScale) => (yScale as d3.ScaleLinear<number, number>)(d.y),

packages/app/src/components/inference/ui/ScatterGraph.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,7 @@ const ScatterGraph = React.memo(
999999
isTracked: trackedConfigIdsRef.current.has(buildPointConfigId(d)),
10001000
runUrl: d.run_url ? updateRepoUrl(d.run_url) : undefined,
10011001
hasTrace: typeof d.id === 'number' ? traceAvailability?.[d.id] === true : false,
1002+
locale,
10021003
}),
10031004
getRulerX: (d: InferenceData, xScale: any) => (xScale as ContinuousScale)(d.x),
10041005
getRulerY: (d: InferenceData, yScale: any) => (yScale as ContinuousScale)(d.y),
@@ -1065,6 +1066,7 @@ const ScatterGraph = React.memo(
10651066
// tooltip content closure (the "View charts" button), so rebuild the
10661067
// config when the presence fetch resolves.
10671068
traceAvailability,
1069+
locale,
10681070
],
10691071
);
10701072

@@ -2008,6 +2010,7 @@ const ScatterGraph = React.memo(
20082010
selectedYAxisMetric,
20092011
hardwareConfig: overlayData.hardwareConfig,
20102012
overlayData,
2013+
locale,
20112014
});
20122015

20132016
overlayPoints

packages/app/src/components/inference/utils/tooltip-utils.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,56 @@ describe('generateTooltipContent', () => {
245245
expect(html).toContain('FP8');
246246
});
247247

248+
it('shows offload type, backend, and version instead of the binary offload mode', () => {
249+
const html = generateTooltipContent(
250+
tooltipConfig({
251+
data: pt({
252+
benchmark_type: 'agentic_traces',
253+
offload_mode: 'on',
254+
kv_offloading: 'dram',
255+
kv_offload_backend: 'mooncake',
256+
kv_offload_backend_version: '0.3.11.post1',
257+
}),
258+
}),
259+
);
260+
expect(html).toContain('<strong>Offload Type:</strong> DRAM');
261+
expect(html).toContain('<strong>Offload Backend:</strong> Mooncake 0.3.11.post1');
262+
expect(html).not.toContain('Offload Mode');
263+
});
264+
265+
it('shows multinode KV transfer and cache-hit metadata for fixed-sequence points', () => {
266+
const html = generateTooltipContent(
267+
tooltipConfig({
268+
data: pt({
269+
benchmark_type: 'single_turn',
270+
is_multinode: true,
271+
kv_p2p_transfer: 'nixl',
272+
server_gpu_cache_hit_rate: 0.875,
273+
}),
274+
}),
275+
);
276+
expect(html).toContain('<strong>KV Cache Transfer Engine:</strong> NIXL');
277+
expect(html).toContain('<strong>GPU Cache Hit Rate:</strong> 87.5%');
278+
});
279+
280+
it('uses Chinese labels for new cache metadata on /zh surfaces', () => {
281+
const html = generateTooltipContent(
282+
tooltipConfig({
283+
locale: 'zh',
284+
data: pt({ kv_offloading: 'dram', kv_offload_backend: 'lmcache' }),
285+
}),
286+
);
287+
expect(html).toContain('<strong>卸载类型:</strong> DRAM');
288+
expect(html).toContain('<strong>卸载后端:</strong> LMCache');
289+
});
290+
291+
it('localizes an empty offload tier on /zh surfaces', () => {
292+
const html = generateTooltipContent(
293+
tooltipConfig({ locale: 'zh', data: pt({ kv_offloading: 'none' }) }),
294+
);
295+
expect(html).toContain('<strong>卸载类型:</strong> 无');
296+
});
297+
248298
it('falls back to hwKey when hardware config entry is missing', () => {
249299
const html = generateTooltipContent(tooltipConfig({ data: pt({ hwKey: 'unknown_gpu' }) }));
250300
expect(html).toContain('unknown_gpu');
@@ -328,6 +378,22 @@ describe('generateOverlayTooltipContent', () => {
328378
expect(html).toContain('Concurrency');
329379
expect(html).toContain('64');
330380
});
381+
382+
it('shows cache metadata for unofficial agentic overlays', () => {
383+
const html = generateOverlayTooltipContent(
384+
overlayConfig({
385+
data: pt({
386+
benchmark_type: 'agentic_traces',
387+
kv_offloading: 'dram',
388+
kv_offload_backend: 'hicache',
389+
server_cpu_cache_hit_rate: 0.42,
390+
}),
391+
}),
392+
);
393+
expect(html).toContain('<strong>Offload Type:</strong> DRAM');
394+
expect(html).toContain('<strong>Offload Backend:</strong> HiCache');
395+
expect(html).toContain('<strong>CPU Cache Hit Rate:</strong> 42.0%');
396+
});
331397
});
332398

333399
// ===========================================================================

packages/app/src/components/inference/utils/tooltipUtils.ts

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { formatNumber, getDisplayLabel } from '@/lib/utils';
22
import { isPersistedBenchmarkId } from '@/lib/benchmark-id';
3+
import type { Locale } from '@/lib/i18n';
34

45
import type { HardwareConfig, InferenceData, OverlayData } from '@/components/inference/types';
56
import { parallelismLabel } from '@/components/inference/utils/parallelism-label';
@@ -29,6 +30,8 @@ export interface TooltipConfig {
2930
* call so we don't ship megabytes of profile JSONL just for this check).
3031
*/
3132
hasTrace?: boolean;
33+
/** Page locale for tooltip metadata labels. Defaults to English. */
34+
locale?: Locale;
3235
}
3336

3437
export interface OverlayTooltipConfig extends TooltipConfig {
@@ -90,24 +93,84 @@ export const fmt = (v: number): string => {
9093
return String(rounded);
9194
};
9295

96+
const CACHE_STRINGS = {
97+
en: {
98+
offloadType: 'Offload Type',
99+
offloadBackend: 'Offload Backend',
100+
transferEngine: 'KV Cache Transfer Engine',
101+
gpuHitRate: 'GPU Cache Hit Rate',
102+
cpuHitRate: 'CPU Cache Hit Rate',
103+
theoreticalHitRate: 'Theoretical Cache Hit Rate',
104+
none: 'None',
105+
},
106+
zh: {
107+
offloadType: '卸载类型',
108+
offloadBackend: '卸载后端',
109+
transferEngine: 'KV Cache 传输引擎',
110+
gpuHitRate: 'GPU Cache 命中率',
111+
cpuHitRate: 'CPU Cache 命中率',
112+
theoreticalHitRate: '理论 Cache 命中率',
113+
none: '无',
114+
},
115+
} as const;
116+
117+
const CACHE_IMPLEMENTATION_LABELS: Record<string, string> = {
118+
hicache: 'HiCache',
119+
lmcache: 'LMCache',
120+
mooncake: 'Mooncake',
121+
mori: 'MoRI',
122+
moriio: 'MoRI-IO',
123+
'mori-io': 'MoRI-IO',
124+
nixl: 'NIXL',
125+
'vllm-native': 'vLLM Native',
126+
'vllm-simple': 'vLLM Simple',
127+
};
128+
129+
const cacheImplementationLabel = (value: string): string =>
130+
CACHE_IMPLEMENTATION_LABELS[value.toLowerCase()] ?? value;
131+
132+
const offloadTypeLabel = (value: string, locale: Locale): string => {
133+
if (value.toLowerCase() === 'dram') return 'DRAM';
134+
if (value.toLowerCase() === 'none') return CACHE_STRINGS[locale].none;
135+
return value.toUpperCase();
136+
};
137+
93138
/**
94-
* Agentic-only tooltip rows: offload mode, KV cache hit rates, request
95-
* success, token totals. Returns an empty string for non-agentic rows.
139+
* Cache configuration and hit-rate rows shared by fixed-sequence, agentic,
140+
* official, comparison, and unofficial-run tooltips.
96141
*/
97-
const generateAgenticHTML = (d: InferenceData): string => {
98-
if (d.benchmark_type !== 'agentic_traces') return '';
99-
142+
const generateCacheMetadataHTML = (d: InferenceData, locale: Locale): string => {
143+
const t = CACHE_STRINGS[locale];
100144
const parts: string[] = [];
101-
if (d.offload_mode) {
102-
parts.push(tooltipLine('Offload Mode', d.offload_mode.toUpperCase()));
145+
if (d.kv_offloading) {
146+
parts.push(tooltipLine(t.offloadType, offloadTypeLabel(d.kv_offloading, locale)));
147+
}
148+
if (d.kv_offload_backend) {
149+
const backend = cacheImplementationLabel(d.kv_offload_backend);
150+
const version = d.kv_offload_backend_version ? ` ${d.kv_offload_backend_version}` : '';
151+
parts.push(tooltipLine(t.offloadBackend, `${backend}${version}`));
152+
}
153+
if (d.is_multinode && d.kv_p2p_transfer) {
154+
parts.push(tooltipLine(t.transferEngine, cacheImplementationLabel(d.kv_p2p_transfer)));
103155
}
104156

105157
const gpuHit = formatPct(d.server_gpu_cache_hit_rate);
106158
const cpuHit = formatPct(d.server_cpu_cache_hit_rate);
107-
const theoHit = formatPct(d.theoretical_cache_hit_rate);
108-
if (gpuHit) parts.push(tooltipLine('GPU Cache Hit Rate', gpuHit));
109-
if (cpuHit) parts.push(tooltipLine('CPU Cache Hit Rate', cpuHit));
110-
if (theoHit) parts.push(tooltipLine('Theoretical Cache Hit Rate', theoHit));
159+
const theoreticalHit = formatPct(d.theoretical_cache_hit_rate);
160+
if (gpuHit) parts.push(tooltipLine(t.gpuHitRate, gpuHit));
161+
if (cpuHit) parts.push(tooltipLine(t.cpuHitRate, cpuHit));
162+
if (theoreticalHit) parts.push(tooltipLine(t.theoreticalHitRate, theoreticalHit));
163+
return parts.join('');
164+
};
165+
166+
/**
167+
* Agentic-only request success and token totals. Cache metadata is rendered
168+
* separately because fixed-sequence rows can carry it too.
169+
*/
170+
const generateAgenticHTML = (d: InferenceData): string => {
171+
if (d.benchmark_type !== 'agentic_traces') return '';
172+
173+
const parts: string[] = [];
111174

112175
if (d.num_requests_total !== undefined && d.num_requests_successful !== undefined) {
113176
const successPct =
@@ -212,6 +275,7 @@ export const generateTooltipContent = (config: TooltipConfig): string => {
212275
runUrl,
213276
hasTrace,
214277
} = config;
278+
const locale = config.locale ?? 'en';
215279

216280
return `
217281
<div style="background: var(--popover); border: 1px solid var(--border); border-radius: 8px; padding: 12px; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); user-select: ${isPinned ? 'text' : 'none'};">
@@ -258,6 +322,7 @@ export const generateTooltipContent = (config: TooltipConfig): string => {
258322
<div style="color: var(--muted-foreground); font-size: 11px; margin-bottom: 4px;">
259323
<strong>Precision:</strong> ${d.precision.toUpperCase()}
260324
</div>
325+
${generateCacheMetadataHTML(d, locale)}
261326
${generateAgenticHTML(d)}
262327
${runLinkHTML(runUrl)}
263328
${viewChartsButtonHTML(isPinned, Boolean(hasTrace), d.id)}
@@ -283,6 +348,7 @@ export const generateTooltipContent = (config: TooltipConfig): string => {
283348
*/
284349
export const generateOverlayTooltipContent = (config: OverlayTooltipConfig): string => {
285350
const { data: d, isPinned, xLabel, yLabel, overlayData } = config;
351+
const locale = config.locale ?? 'en';
286352
const hwConfig = overlayData.hardwareConfig[d.hwKey];
287353
const perRow = overlayData.getRunForRow?.(d);
288354
const branch = perRow?.branch ?? overlayData.label;
@@ -316,6 +382,7 @@ export const generateOverlayTooltipContent = (config: OverlayTooltipConfig): str
316382
<div style="color: var(--muted-foreground); font-size: 11px; margin-bottom: 4px;">
317383
<strong>Precision:</strong> ${d.precision.toUpperCase()}
318384
</div>
385+
${generateCacheMetadataHTML(d, locale)}
319386
${generateAgenticHTML(d)}
320387
</div>
321388
`;
@@ -339,6 +406,7 @@ export const generateGPUGraphTooltipContent = (config: TooltipConfig): string =>
339406
runUrl,
340407
hasTrace,
341408
} = config;
409+
const locale = config.locale ?? 'en';
342410

343411
return `
344412
<div style="background: var(--popover); border: 1px solid var(--border); border-radius: 8px; padding: 12px; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); user-select: ${isPinned ? 'text' : 'none'};">
@@ -385,6 +453,7 @@ export const generateGPUGraphTooltipContent = (config: TooltipConfig): string =>
385453
<div style="color: var(--muted-foreground); font-size: 11px; margin-bottom: 4px;">
386454
<strong>Precision:</strong> ${d.precision.toUpperCase()}
387455
</div>
456+
${generateCacheMetadataHTML(d, locale)}
388457
${generateAgenticHTML(d)}
389458
${runLinkHTML(runUrl)}
390459
${viewChartsButtonHTML(isPinned, Boolean(hasTrace), d.id)}

packages/app/src/lib/benchmark-transform.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,24 @@ describe('rowToAggDataEntry', () => {
123123
expect(entryNull.image).toBeUndefined();
124124
});
125125

126+
it('passes runtime cache metadata through to chart points', () => {
127+
const entry = rowToAggDataEntry(
128+
makeRow({
129+
metrics: {
130+
kv_offloading: 'dram',
131+
kv_offload_backend: 'mooncake',
132+
kv_offload_backend_version: '0.3.11.post1',
133+
kv_p2p_transfer: 'nixl',
134+
} as unknown as BenchmarkRow['metrics'],
135+
}),
136+
);
137+
138+
expect(entry.kv_offloading).toBe('dram');
139+
expect(entry.kv_offload_backend).toBe('mooncake');
140+
expect(entry.kv_offload_backend_version).toBe('0.3.11.post1');
141+
expect(entry.kv_p2p_transfer).toBe('nixl');
142+
});
143+
126144
it('passes through measured power telemetry fields when present', () => {
127145
const entry = rowToAggDataEntry(
128146
makeRow({

packages/app/src/lib/benchmark-transform.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ export function rowToAggDataEntry(row: BenchmarkRow): AggDataEntry {
5656
const offloadMode =
5757
row.offload_mode ??
5858
(typeof rawMetrics.offload_mode === 'string' ? rawMetrics.offload_mode : undefined);
59+
const stringMetric = (key: string): string | undefined => {
60+
const value = rawMetrics[key];
61+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
62+
};
5963
// Postgres bigint comes through the SQL client as a string; coerce it. Overlay
6064
// rows (transformed live from raw artifacts) carry no id, so `Number(undefined)`
6165
// is NaN — collapse any non-persisted value to undefined so downstream link /
@@ -160,6 +164,10 @@ export function rowToAggDataEntry(row: BenchmarkRow): AggDataEntry {
160164
isl: row.isl,
161165
osl: row.osl,
162166
offload_mode: offloadMode,
167+
kv_offloading: stringMetric('kv_offloading'),
168+
kv_offload_backend: stringMetric('kv_offload_backend'),
169+
kv_offload_backend_version: stringMetric('kv_offload_backend_version'),
170+
kv_p2p_transfer: stringMetric('kv_p2p_transfer'),
163171
server_gpu_cache_hit_rate: m.server_gpu_cache_hit_rate,
164172
server_cpu_cache_hit_rate: m.server_cpu_cache_hit_rate,
165173
theoretical_cache_hit_rate: m.theoretical_cache_hit_rate,

0 commit comments

Comments
 (0)