Skip to content
15 changes: 13 additions & 2 deletions packages/app/cypress/e2e/gpu-compare-agentic-detail.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,16 @@ const agenticBenchmarks = AGENTIC_HARDWARE.flatMap((g) =>
isl: null,
osl: null,
conc,
offload_mode: 'off',
offload_mode: 'on',
benchmark_type: 'agentic_traces',
image: 'vllm/vllm-openai:v0.9.0',
metrics: agenticMetrics(conc),
metrics: {
...agenticMetrics(conc),
kv_offloading: 'dram',
kv_offload_backend: 'mooncake',
kv_offload_backend_version: '0.3.11.post1',
server_gpu_cache_hit_rate: 0.875,
},
workers: null,
date: AGENTIC_DATE,
run_url: null,
Expand Down Expand Up @@ -176,6 +182,11 @@ describe('GPU comparison agentic point detail', () => {
});

cy.get('[data-chart-tooltip]:visible').should('have.length', 1);
cy.get('[data-chart-tooltip]:visible')
.should('contain', 'Offload Type: DRAM')
.and('contain', 'Offload Backend: Mooncake 0.3.11.post1')
.and('contain', 'GPU Cache Hit Rate: 87.5%')
.and('not.contain', 'Offload Mode');
cy.get('[data-chart-tooltip]:visible [data-action="view-charts"]')
.should('be.visible')
.then(($link) => {
Expand Down
21 changes: 21 additions & 0 deletions packages/app/cypress/e2e/unofficial-watermark.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ describe('Unofficial-run watermark', () => {
],
benchmarks: benchmarks.map((row: Record<string, unknown>) => ({
...row,
is_multinode: true,
metrics: {
...(row.metrics as Record<string, unknown> | undefined),
kv_offloading: 'dram',
kv_offload_backend: 'hicache',
kv_p2p_transfer: 'nixl',
server_gpu_cache_hit_rate: 0.875,
},
run_url: runUrl,
})),
evaluations: [],
Expand Down Expand Up @@ -58,6 +66,19 @@ describe('Unofficial-run watermark', () => {
cy.wrap($image).parent('svg').find('.unofficial-watermark-image').should('have.length', 1);
});

cy.get('[data-testid="scatter-graph"] .unofficial-overlay-pt')
.first()
.then(($point) => {
$point[0].dispatchEvent(new MouseEvent('mouseenter'));
const tooltip = $point[0].ownerDocument.querySelector<HTMLElement>('[data-chart-tooltip]');
expect(tooltip).not.to.equal(null);
expect(tooltip!.style.display).to.equal('block');
expect(tooltip).to.contain.text('Offload Type: DRAM');
expect(tooltip).to.contain.text('Offload Backend: HiCache');
expect(tooltip).to.contain.text('KV Cache Transfer Engine: NIXL');
expect(tooltip).to.contain.text('GPU Cache Hit Rate: 87.5%');
});

cy.get('[data-testid="scatter-graph"]').first().scrollIntoView();
cy.screenshot('unofficial-watermark', { capture: 'viewport' });

Expand Down
10 changes: 9 additions & 1 deletion packages/app/src/components/inference/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,17 @@ export interface AggDataEntry {
isl?: number | null;
/** OSL in tokens — null for agentic_traces. */
osl?: number | null;
// ── Agentic-only fields (populated from metrics JSONB for `agentic_traces` rows) ──
// ── Runtime cache metadata (populated from metrics JSONB when emitted) ──
/** "on" | "off" — whether KV cache offload to CPU was enabled. */
offload_mode?: string;
/** Offload tier/type, for example `dram` or `none`. */
kv_offloading?: string;
/** Offload implementation, for example `mooncake`, `lmcache`, or `hicache`. */
kv_offload_backend?: string;
/** Optional version independently declared for the offload backend. */
kv_offload_backend_version?: string;
/** P2P engine used to move KV state between workers on multinode runs. */
kv_p2p_transfer?: string;
/** Actual server-observed GPU prefix-cache hit rate (0..1). */
server_gpu_cache_hit_rate?: number;
/** Actual server-observed CPU prefix-cache hit rate (0..1). */
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/components/inference/ui/GPUGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,7 @@ const GPUGraph = React.memo(
hardwareConfig,
runUrl: d.run_url ? updateRepoUrl(d.run_url) : undefined,
hasTrace: typeof d.id === 'number' ? traceAvailability?.[d.id] === true : false,
locale,
}),
getRulerX: (d, xScale) => (xScale as d3.ScaleLinear<number, number>)(d.x),
getRulerY: (d, yScale) => (yScale as d3.ScaleLinear<number, number>)(d.y),
Expand Down
6 changes: 4 additions & 2 deletions packages/app/src/components/inference/ui/ScatterGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,7 @@ const ScatterGraph = React.memo(
isTracked: trackedConfigIdsRef.current.has(buildPointConfigId(d)),
runUrl: d.run_url ? updateRepoUrl(d.run_url) : undefined,
hasTrace: typeof d.id === 'number' ? traceAvailability?.[d.id] === true : false,
locale,
}),
getRulerX: (d: InferenceData, xScale: any) => (xScale as ContinuousScale)(d.x),
getRulerY: (d: InferenceData, yScale: any) => (yScale as ContinuousScale)(d.y),
Expand Down Expand Up @@ -1067,6 +1068,7 @@ const ScatterGraph = React.memo(
// tooltip content closure (the "View charts" button), so rebuild the
// config when the presence fetch resolves.
traceAvailability,
locale,
],
);

Expand Down Expand Up @@ -1999,8 +2001,7 @@ const ScatterGraph = React.memo(
// Overlay tooltip handlers
const svgNode = ctx.layout.svg.node()!;
const container = svgNode.parentElement as HTMLDivElement;
const tooltipDiv = svgNode.nextElementSibling as HTMLDivElement;
const tooltip = d3.select(tooltipDiv);
const tooltip = d3.select(ctx.tooltipElement);

const createOverlayConfig = (d: InferenceData, pinned: boolean) => ({
data: d,
Expand All @@ -2010,6 +2011,7 @@ const ScatterGraph = React.memo(
selectedYAxisMetric,
hardwareConfig: overlayData.hardwareConfig,
overlayData,
locale,
Comment thread
cursor[bot] marked this conversation as resolved.
});

overlayPoints
Expand Down
66 changes: 66 additions & 0 deletions packages/app/src/components/inference/utils/tooltip-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,56 @@ describe('generateTooltipContent', () => {
expect(html).toContain('FP8');
});

it('shows offload type, backend, and version instead of the binary offload mode', () => {
const html = generateTooltipContent(
tooltipConfig({
data: pt({
benchmark_type: 'agentic_traces',
offload_mode: 'on',
kv_offloading: 'dram',
kv_offload_backend: 'mooncake',
kv_offload_backend_version: '0.3.11.post1',
}),
}),
);
expect(html).toContain('<strong>Offload Type:</strong> DRAM');
expect(html).toContain('<strong>Offload Backend:</strong> Mooncake 0.3.11.post1');
expect(html).not.toContain('Offload Mode');
});

it('shows multinode KV transfer and cache-hit metadata for fixed-sequence points', () => {
const html = generateTooltipContent(
tooltipConfig({
data: pt({
benchmark_type: 'single_turn',
is_multinode: true,
kv_p2p_transfer: 'nixl',
server_gpu_cache_hit_rate: 0.875,
}),
}),
);
expect(html).toContain('<strong>KV Cache Transfer Engine:</strong> NIXL');
expect(html).toContain('<strong>GPU Cache Hit Rate:</strong> 87.5%');
});

it('uses Chinese labels for new cache metadata on /zh surfaces', () => {
const html = generateTooltipContent(
tooltipConfig({
locale: 'zh',
data: pt({ kv_offloading: 'dram', kv_offload_backend: 'lmcache' }),
}),
);
expect(html).toContain('<strong>卸载类型:</strong> DRAM');
expect(html).toContain('<strong>卸载后端:</strong> LMCache');
});

it('localizes an empty offload tier on /zh surfaces', () => {
const html = generateTooltipContent(
tooltipConfig({ locale: 'zh', data: pt({ kv_offloading: 'none' }) }),
);
expect(html).toContain('<strong>卸载类型:</strong> 无');
});

it('falls back to hwKey when hardware config entry is missing', () => {
const html = generateTooltipContent(tooltipConfig({ data: pt({ hwKey: 'unknown_gpu' }) }));
expect(html).toContain('unknown_gpu');
Expand Down Expand Up @@ -328,6 +378,22 @@ describe('generateOverlayTooltipContent', () => {
expect(html).toContain('Concurrency');
expect(html).toContain('64');
});

it('shows cache metadata for unofficial agentic overlays', () => {
const html = generateOverlayTooltipContent(
overlayConfig({
data: pt({
benchmark_type: 'agentic_traces',
kv_offloading: 'dram',
kv_offload_backend: 'hicache',
server_cpu_cache_hit_rate: 0.42,
}),
}),
);
expect(html).toContain('<strong>Offload Type:</strong> DRAM');
expect(html).toContain('<strong>Offload Backend:</strong> HiCache');
expect(html).toContain('<strong>CPU Cache Hit Rate:</strong> 42.0%');
});
});

// ===========================================================================
Expand Down
91 changes: 80 additions & 11 deletions packages/app/src/components/inference/utils/tooltipUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { formatNumber, getDisplayLabel } from '@/lib/utils';
import { isPersistedBenchmarkId } from '@/lib/benchmark-id';
import type { Locale } from '@/lib/i18n';

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

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

const CACHE_STRINGS = {
en: {
offloadType: 'Offload Type',
offloadBackend: 'Offload Backend',
transferEngine: 'KV Cache Transfer Engine',
gpuHitRate: 'GPU Cache Hit Rate',
cpuHitRate: 'CPU Cache Hit Rate',
theoreticalHitRate: 'Theoretical Cache Hit Rate',
none: 'None',
},
zh: {
offloadType: '卸载类型',
offloadBackend: '卸载后端',
transferEngine: 'KV Cache 传输引擎',
gpuHitRate: 'GPU Cache 命中率',
cpuHitRate: 'CPU Cache 命中率',
theoreticalHitRate: '理论 Cache 命中率',
none: '无',
},
} as const;

const CACHE_IMPLEMENTATION_LABELS: Record<string, string> = {
hicache: 'HiCache',
lmcache: 'LMCache',
mooncake: 'Mooncake',
mori: 'MoRI',
moriio: 'MoRI-IO',
'mori-io': 'MoRI-IO',
nixl: 'NIXL',
'vllm-native': 'vLLM Native',
'vllm-simple': 'vLLM Simple',
};

const cacheImplementationLabel = (value: string): string =>
CACHE_IMPLEMENTATION_LABELS[value.toLowerCase()] ?? value;

const offloadTypeLabel = (value: string, locale: Locale): string => {
if (value.toLowerCase() === 'dram') return 'DRAM';
if (value.toLowerCase() === 'none') return CACHE_STRINGS[locale].none;
return value.toUpperCase();
};

/**
* Agentic-only tooltip rows: offload mode, KV cache hit rates, request
* success, token totals. Returns an empty string for non-agentic rows.
* Cache configuration and hit-rate rows shared by fixed-sequence, agentic,
* official, comparison, and unofficial-run tooltips.
*/
const generateAgenticHTML = (d: InferenceData): string => {
if (d.benchmark_type !== 'agentic_traces') return '';

const generateCacheMetadataHTML = (d: InferenceData, locale: Locale): string => {
const t = CACHE_STRINGS[locale];
const parts: string[] = [];
if (d.offload_mode) {
parts.push(tooltipLine('Offload Mode', d.offload_mode.toUpperCase()));
if (d.kv_offloading) {
parts.push(tooltipLine(t.offloadType, offloadTypeLabel(d.kv_offloading, locale)));
}
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.
if (d.kv_offload_backend) {
const backend = cacheImplementationLabel(d.kv_offload_backend);
const version = d.kv_offload_backend_version ? ` ${d.kv_offload_backend_version}` : '';
parts.push(tooltipLine(t.offloadBackend, `${backend}${version}`));
}
if (d.is_multinode && d.kv_p2p_transfer) {
parts.push(tooltipLine(t.transferEngine, cacheImplementationLabel(d.kv_p2p_transfer)));
}

const gpuHit = formatPct(d.server_gpu_cache_hit_rate);
const cpuHit = formatPct(d.server_cpu_cache_hit_rate);
const theoHit = formatPct(d.theoretical_cache_hit_rate);
if (gpuHit) parts.push(tooltipLine('GPU Cache Hit Rate', gpuHit));
if (cpuHit) parts.push(tooltipLine('CPU Cache Hit Rate', cpuHit));
if (theoHit) parts.push(tooltipLine('Theoretical Cache Hit Rate', theoHit));
const theoreticalHit = formatPct(d.theoretical_cache_hit_rate);
if (gpuHit) parts.push(tooltipLine(t.gpuHitRate, gpuHit));
if (cpuHit) parts.push(tooltipLine(t.cpuHitRate, cpuHit));
if (theoreticalHit) parts.push(tooltipLine(t.theoreticalHitRate, theoreticalHit));
return parts.join('');
};

/**
* Agentic-only request success and token totals. Cache metadata is rendered
* separately because fixed-sequence rows can carry it too.
*/
const generateAgenticHTML = (d: InferenceData): string => {
if (d.benchmark_type !== 'agentic_traces') return '';

const parts: string[] = [];

if (d.num_requests_total !== undefined && d.num_requests_successful !== undefined) {
const successPct =
Expand Down Expand Up @@ -212,6 +275,7 @@ export const generateTooltipContent = (config: TooltipConfig): string => {
runUrl,
hasTrace,
} = config;
const locale = config.locale ?? 'en';

return `
<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'};">
Expand Down Expand Up @@ -258,6 +322,7 @@ export const generateTooltipContent = (config: TooltipConfig): string => {
<div style="color: var(--muted-foreground); font-size: 11px; margin-bottom: 4px;">
<strong>Precision:</strong> ${d.precision.toUpperCase()}
</div>
${generateCacheMetadataHTML(d, locale)}
${generateAgenticHTML(d)}
${runLinkHTML(runUrl)}
${viewChartsButtonHTML(isPinned, Boolean(hasTrace), d.id)}
Expand All @@ -283,6 +348,7 @@ export const generateTooltipContent = (config: TooltipConfig): string => {
*/
export const generateOverlayTooltipContent = (config: OverlayTooltipConfig): string => {
const { data: d, isPinned, xLabel, yLabel, overlayData } = config;
const locale = config.locale ?? 'en';
const hwConfig = overlayData.hardwareConfig[d.hwKey];
const perRow = overlayData.getRunForRow?.(d);
const branch = perRow?.branch ?? overlayData.label;
Expand Down Expand Up @@ -316,6 +382,7 @@ export const generateOverlayTooltipContent = (config: OverlayTooltipConfig): str
<div style="color: var(--muted-foreground); font-size: 11px; margin-bottom: 4px;">
<strong>Precision:</strong> ${d.precision.toUpperCase()}
</div>
${generateCacheMetadataHTML(d, locale)}
${generateAgenticHTML(d)}
</div>
`;
Expand All @@ -339,6 +406,7 @@ export const generateGPUGraphTooltipContent = (config: TooltipConfig): string =>
runUrl,
hasTrace,
} = config;
const locale = config.locale ?? 'en';

return `
<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'};">
Expand Down Expand Up @@ -385,6 +453,7 @@ export const generateGPUGraphTooltipContent = (config: TooltipConfig): string =>
<div style="color: var(--muted-foreground); font-size: 11px; margin-bottom: 4px;">
<strong>Precision:</strong> ${d.precision.toUpperCase()}
</div>
${generateCacheMetadataHTML(d, locale)}
${generateAgenticHTML(d)}
${runLinkHTML(runUrl)}
${viewChartsButtonHTML(isPinned, Boolean(hasTrace), d.id)}
Expand Down
18 changes: 18 additions & 0 deletions packages/app/src/lib/benchmark-transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,24 @@ describe('rowToAggDataEntry', () => {
expect(entryNull.image).toBeUndefined();
});

it('passes runtime cache metadata through to chart points', () => {
const entry = rowToAggDataEntry(
makeRow({
metrics: {
kv_offloading: 'dram',
kv_offload_backend: 'mooncake',
kv_offload_backend_version: '0.3.11.post1',
kv_p2p_transfer: 'nixl',
} as unknown as BenchmarkRow['metrics'],
}),
);

expect(entry.kv_offloading).toBe('dram');
expect(entry.kv_offload_backend).toBe('mooncake');
expect(entry.kv_offload_backend_version).toBe('0.3.11.post1');
expect(entry.kv_p2p_transfer).toBe('nixl');
});

it('passes through measured power telemetry fields when present', () => {
const entry = rowToAggDataEntry(
makeRow({
Expand Down
Loading