Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/data-transforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ This is the most complex and bug-prone part of the pipeline. A bad hardware key

1. Base GPU: `entry.hw.split('-')[0]` strips any `-DP` / `-MN` variant suffix from the hardware field (e.g. `"h100-8"` → `"h100"`).
2. Framework suffix: appends `_${entry.framework}`. The direct key (`h100_trt`) is tested via `isKnownGpu()` (checks whether the base GPU exists in `HW_REGISTRY`). If the direct key's base is unknown and `entry.disagg` is true, a `-disagg` variant is tried.
3. Spec decoding suffix: if `entry.mtp === 'on'` or `entry.spec_decoding === 'mtp'`, appends `_mtp`. Otherwise, any non-`'none'` `spec_decoding` value is appended as-is (e.g. `_eagle`).
3. Spec decoding suffix: for fixed-sequence rows, if `entry.mtp === 'on'` or `entry.spec_decoding === 'mtp'`, appends `_mtp`. Otherwise, any non-`'none'` `spec_decoding` value is appended as-is (e.g. `_eagle`). Agentic rows deliberately omit this suffix because one production curve may combine speculative and standard-decoding points; `spec_decoding` remains on each point for filtering, tooltip metadata, and point-level identity.

The resulting key's base GPU must exist in `HW_REGISTRY`. Display fields (label, suffix, gpu tooltip) are derived dynamically by `getHardwareConfig()`. Unrecognised base GPUs fall back to the `unknown` hardware config.

Expand Down
42 changes: 40 additions & 2 deletions packages/app/src/app/api/v1/benchmarks/history/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,39 @@ describe('GET /api/v1/benchmarks/history', () => {
expect(res.status).toBe(200);
const body = await res.json();
expect(body).toEqual(mockRows);
expect(mockGetAllBenchmarksForHistory).toHaveBeenCalledWith('mock-sql', ['dsr1'], 1024, 1024);
expect(mockGetAllBenchmarksForHistory).toHaveBeenCalledWith(
'mock-sql',
['dsr1'],
1024,
1024,
undefined,
);
});

it('returns agentic history without numeric sequence lengths', async () => {
const mockRows = [{ date: '2026-03-01', benchmark_type: 'agentic_traces' }];
mockGetAllBenchmarksForHistory.mockResolvedValueOnce(mockRows);

const res = await GET(
req('/api/v1/benchmarks/history?model=DeepSeek-R1-0528&benchmarkType=agentic_traces'),
);
expect(res.status).toBe(200);
expect(await res.json()).toEqual(mockRows);
expect(mockGetAllBenchmarksForHistory).toHaveBeenCalledWith(
'mock-sql',
['dsr1'],
null,
null,
'agentic_traces',
);
});

it('rejects unsupported benchmark types', async () => {
const res = await GET(
req('/api/v1/benchmarks/history?model=DeepSeek-R1-0528&benchmarkType=single_turn'),
);
expect(res.status).toBe(400);
expect(await res.json()).toEqual({ error: 'Unsupported benchmarkType' });
});

it('returns 500 when query throws', async () => {
Expand All @@ -101,6 +133,12 @@ describe('GET /api/v1/benchmarks/history', () => {
expect(res.status).toBe(200);
const body = await res.json();
expect(body).toEqual([]);
expect(mockGetAllBenchmarksForHistory).toHaveBeenCalledWith('mock-sql', ['dsr1'], 1024, 8192);
expect(mockGetAllBenchmarksForHistory).toHaveBeenCalledWith(
'mock-sql',
['dsr1'],
1024,
8192,
undefined,
);
});
});
31 changes: 23 additions & 8 deletions packages/app/src/app/api/v1/benchmarks/history/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,28 @@ import { loadFixture } from '@/lib/test-fixtures';
export const dynamic = 'force-dynamic';

const getCachedBenchmarkHistory = cachedQuery(
(modelKeys: string[], isl: number, osl: number) =>
getAllBenchmarksForHistory(getDb(), modelKeys, isl, osl),
'benchmark-history',
(modelKeys: string[], isl: number | null, osl: number | null, benchmarkType?: string) =>
getAllBenchmarksForHistory(getDb(), modelKeys, isl, osl, benchmarkType),
'benchmark-history-v2',
{ blobOnly: true },
);

export async function GET(request: NextRequest) {
const model = request.nextUrl.searchParams.get('model') ?? '';
const isl = Number(request.nextUrl.searchParams.get('isl'));
const osl = Number(request.nextUrl.searchParams.get('osl'));

if (!model || !isl || !osl) {
const rawIsl = request.nextUrl.searchParams.get('isl');
const rawOsl = request.nextUrl.searchParams.get('osl');
const benchmarkType = request.nextUrl.searchParams.get('benchmarkType') ?? undefined;
const isl = rawIsl === null ? null : Number(rawIsl);
const osl = rawOsl === null ? null : Number(rawOsl);
const isAgentic = benchmarkType === 'agentic_traces';

if (!model) {
return NextResponse.json({ error: 'model, isl, and osl are required' }, { status: 400 });
}
if (benchmarkType !== undefined && !isAgentic) {
return NextResponse.json({ error: 'Unsupported benchmarkType' }, { status: 400 });
}
if (!isAgentic && (!isl || !osl)) {
return NextResponse.json({ error: 'model, isl, and osl are required' }, { status: 400 });
}
if (FIXTURES_MODE) return cachedJson(loadFixture('benchmarks-history'));
Expand All @@ -32,7 +42,12 @@ export async function GET(request: NextRequest) {
if (!modelKeys || modelKeys.length === 0) {
return NextResponse.json({ error: 'Unknown model' }, { status: 400 });
}
const rows = await getCachedBenchmarkHistory(modelKeys, isl, osl);
const rows = await getCachedBenchmarkHistory(
modelKeys,
isAgentic ? null : isl,
isAgentic ? null : osl,
benchmarkType,
);
return cachedJson(rows);
} catch (error) {
console.error('Error fetching benchmark history:', error);
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/app/api/v1/workflow-info/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const getCachedWorkflowInfo = cachedQuery(async (date: string) => {
getRunConfigsByDate(sql, date),
]);
return { runs, changelogs, configs, runConfigs };
}, 'workflow-info');
}, 'workflow-info-v2');

export async function GET(request: NextRequest) {
const date = request.nextUrl.searchParams.get('date') ?? '';
Expand Down
40 changes: 27 additions & 13 deletions packages/app/src/components/inference/InferenceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import {
comparisonExclusion as resolveComparisonExclusion,
} from './utils/comparison-exclusion';
import { resolveLabelState, serializeLabelState } from './utils/label-defaults';
import { trackedConfigIdentity } from './utils/point-identity';
import {
EMPTY_QUICK_FILTERS,
parseDeploymentModes,
Expand Down Expand Up @@ -505,7 +506,13 @@ export function InferenceProvider({
if (rowToSequence(r) !== effectiveSequence) return false;
if (!effectivePrecisions.includes(r.precision)) return false;
if (!r.hardware) return false;
const hwKey = buildAvailabilityHwKey(r.hardware, r.framework, r.spec_method, r.disagg);
const hwKey = buildAvailabilityHwKey(
r.hardware,
r.framework,
r.spec_method,
r.disagg,
r.benchmark_type,
);
return selectedGPUs.includes(hwKey);
});
const dates = [...new Set(rows.map((r) => r.date))].toSorted();
Expand All @@ -530,7 +537,13 @@ export function InferenceProvider({
if (rowToSequence(r) !== effectiveSequence) continue;
if (!effectivePrecisions.includes(r.precision)) continue;
if (!r.hardware) continue;
const hwKey = buildAvailabilityHwKey(r.hardware, r.framework, r.spec_method, r.disagg);
const hwKey = buildAvailabilityHwKey(
r.hardware,
r.framework,
r.spec_method,
r.disagg,
r.benchmark_type,
);
Comment thread
cursor[bot] marked this conversation as resolved.
if (isKnownGpu(hwKey)) hwKeys.add(hwKey);
}
return [...hwKeys]
Expand All @@ -542,27 +555,26 @@ export function InferenceProvider({
}, [availabilityRows, dbModelKeys, effectiveSequence, effectivePrecisions, selectedModel]);

// --- Tracked config functions ---
const buildTrackedConfigId = useCallback((point: InferenceData): string => {
let key = `${point.hwKey}|${point.precision}|${point.tp}|${point.conc}`;
if (point.disagg) {
key += `|disagg|${point.num_prefill_gpu ?? 0}|${point.num_decode_gpu ?? 0}`;
}
return key;
}, []);

const addTrackedConfig = useCallback(
(point: InferenceData, chartType: string) => {
setTrackedConfigs((prev) => {
const id = buildTrackedConfigId(point);
const id = trackedConfigIdentity(point);
if (prev.some((c) => c.id === id)) {
return prev.filter((c) => c.id !== id);
}
if (prev.length >= 6) return prev;

const hwConfig = hardwareConfig[point.hwKey];
const label = hwConfig
let label = hwConfig
? `${getDisplayLabel(hwConfig)} — TP${point.tp} conc=${point.conc} ${point.precision.toUpperCase()}`
: `${point.hwKey} — TP${point.tp} conc=${point.conc} ${point.precision.toUpperCase()}`;
if (point.benchmark_type === 'agentic_traces') {
const specLabel =
point.spec_decoding && point.spec_decoding !== 'none'
? point.spec_decoding.toUpperCase()
: 'STP';
label += ` ${specLabel}`;
}

const color = TABLEAU_10[prev.length % TABLEAU_10.length];
return [
Expand All @@ -579,11 +591,13 @@ export function InferenceProvider({
disagg: point.disagg,
num_prefill_gpu: point.num_prefill_gpu,
num_decode_gpu: point.num_decode_gpu,
benchmark_type: point.benchmark_type,
spec_decoding: point.spec_decoding,
},
];
});
},
[buildTrackedConfigId, hardwareConfig],
[hardwareConfig],
);

const removeTrackedConfig = useCallback((id: string) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, it, expect } from 'vitest';

import chartDefinitions from '@/components/inference/inference-chart-config.json';
import { dedupeAgenticHistoryRuns } from '@/lib/benchmark-run-selection';

import {
applyAgenticPercentileToXLabel,
Expand All @@ -19,7 +20,10 @@ interface DedupeInput {
disagg: boolean;
precision: string;
offload_mode?: string | null;
benchmark_type?: string;
date: string;
workflow_run_id?: number;
run_started_at?: string | null;
}

const drow = (over: Partial<DedupeInput> = {}): DedupeInput => ({
Expand Down Expand Up @@ -81,6 +85,84 @@ describe('dedupeRowsToLatestPerConfig', () => {
];
expect(dedupeRowsToLatestPerConfig(rows).map((r) => r.id)).toEqual([2]);
});

it('dedupes mixed agentic spec methods as one curve', () => {
const rows = [
drow({ id: 1, benchmark_type: 'agentic_traces', spec_method: 'none', date: '2026-06-01' }),
drow({ id: 2, benchmark_type: 'agentic_traces', spec_method: 'mtp', date: '2026-06-03' }),
drow({ id: 3, benchmark_type: 'agentic_traces', spec_method: 'eagle', date: '2026-06-03' }),
];

expect(dedupeRowsToLatestPerConfig(rows).map((r) => r.id)).toEqual([2, 3]);
});

it('continues deduping fixed-sequence spec methods independently', () => {
const rows = [
drow({ id: 1, benchmark_type: 'single_turn', spec_method: 'none', date: '2026-06-01' }),
drow({ id: 2, benchmark_type: 'single_turn', spec_method: 'mtp', date: '2026-06-03' }),
];

expect(dedupeRowsToLatestPerConfig(rows).map((r) => r.id)).toEqual([1, 2]);
});

it('keeps mixed agentic points from only the newest same-day workflow run', () => {
const rows = [
drow({
id: 1,
benchmark_type: 'agentic_traces',
spec_method: 'none',
workflow_run_id: 10,
run_started_at: '2026-06-03T10:00:00Z',
}),
drow({
id: 2,
benchmark_type: 'agentic_traces',
spec_method: 'mtp',
workflow_run_id: 10,
run_started_at: '2026-06-03T10:00:00Z',
}),
drow({
id: 3,
benchmark_type: 'agentic_traces',
spec_method: 'mtp',
workflow_run_id: 11,
run_started_at: '2026-06-03T12:00:00Z',
}),
];

expect(dedupeRowsToLatestPerConfig(rows).map((r) => r.id)).toEqual([3]);
});
});

describe('dedupeAgenticHistoryRuns', () => {
it('keeps the newest agentic workflow per series on each date', () => {
const rows = [
drow({
id: 1,
benchmark_type: 'agentic_traces',
spec_method: 'none',
workflow_run_id: 20,
run_started_at: '2026-06-01T10:00:00Z',
}),
drow({
id: 2,
benchmark_type: 'agentic_traces',
spec_method: 'mtp',
workflow_run_id: 21,
run_started_at: '2026-06-01T12:00:00Z',
}),
drow({
id: 3,
benchmark_type: 'agentic_traces',
spec_method: 'none',
date: '2026-06-02',
workflow_run_id: 22,
run_started_at: '2026-06-02T10:00:00Z',
}),
];

expect(dedupeAgenticHistoryRuns(rows).map((row) => row.id)).toEqual([2, 3]);
});
});

describe('buildComparisonDates', () => {
Expand Down
51 changes: 11 additions & 40 deletions packages/app/src/components/inference/hooks/useChartData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import {
hardwareKeyMatchesAnyBase,
} from '@/lib/constants';
import { mergeRunScopedRows, transformBenchmarkRows } from '@/lib/benchmark-transform';
import {
dedupeAgenticHistoryRuns,
dedupeRowsToLatestPerConfig,
} from '@/lib/benchmark-run-selection';
import { Sequence, type Model } from '@/lib/data-mappings';
import { calculateCostsForGpus, calculatePowerForGpus } from '@/lib/utils';
import { resolveXAxisField } from '@/components/inference/utils/resolveXAxisField';
Expand Down Expand Up @@ -136,41 +140,7 @@ export function applyAgenticPercentileToXLabel(label: string, pctlWord: string):
: `${pctlWord} ${label}`;
}

/** The dedup key fields a chart series is identified by. */
interface DedupeRow {
hardware: string;
framework: string;
spec_method: string;
disagg: boolean;
precision: string;
offload_mode?: string | null;
date: string;
}

// offload_mode normalized `?? 'off'` to match the SQL layer's getBenchmarksForRun
// lineKey — agentic offload=on and offload=off are distinct series.
const dedupeSeriesKey = (r: DedupeRow): string =>
`${r.hardware}|${r.framework}|${r.spec_method}|${r.disagg}|${r.precision}|${r.offload_mode ?? 'off'}`;

/**
* For each series — (hardware, framework, spec_method, disagg, precision,
* offload_mode) — keep only the rows from that series' most recent date. When
* parallelism settings change between runs, old config_ids create stale points
* under the same legend line; dropping all-but-latest removes them.
*
* Without `offload_mode` in the key, an offload=on sweep ingested on a LATER date
* than the offload=off sweep would win the shared group and silently drop the
* (earlier-dated) offload=off variant — a data-loss regression.
*/
export function dedupeRowsToLatestPerConfig<T extends DedupeRow>(rows: T[]): T[] {
const maxDatePerGroup = new Map<string, string>();
for (const r of rows) {
const k = dedupeSeriesKey(r);
const cur = maxDatePerGroup.get(k);
if (!cur || r.date > cur) maxDatePerGroup.set(k, r.date);
}
return rows.filter((r) => r.date === maxDatePerGroup.get(dedupeSeriesKey(r)));
}
export { dedupeRowsToLatestPerConfig };

export function useChartData(
selectedModel: Model,
Expand Down Expand Up @@ -301,11 +271,12 @@ export function useChartData(
selectedRunDate ? { ...r, date: selectedRunDate, actualDate: r.date } : r,
);
if (comparisonDates.length === 0) return mainRows;
const extraRows = comparisonQueries.flatMap((q, i) =>
(q.data ?? [])
.filter(seqFilter)
.map((r) => ({ ...r, date: comparisonDates[i], actualDate: r.date })),
);
const extraRows = comparisonQueries.flatMap((q, i) => {
const filtered = (q.data ?? []).filter(seqFilter);
const selected =
selectedSequence === Sequence.AgenticTraces ? dedupeAgenticHistoryRuns(filtered) : filtered;
return selected.map((r) => ({ ...r, date: comparisonDates[i], actualDate: r.date }));
});
return [...mainRows, ...extraRows];
}, [allRows, selectedSequence, comparisonDates, comparisonDataKey, selectedRunDate]);

Expand Down
Loading