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
4 changes: 2 additions & 2 deletions packages/app/src/components/ai-chart/prompt-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ Open-source ML inference benchmark dashboard. Automated CI runs test real servin
**Sequences**: 1k/1k, 1k/8k, 8k/1k (input/output token lengths)

**Benchmark y-axis metrics**: ${Y_METRIC_LIST}
Throughput: y_tpPerGpu (total tok/s/gpu, DEFAULT), y_outputTputPerGpu, y_inputTputPerGpu
Efficiency: y_tpPerMw (tok/s/MW)
Throughput: y_tpPerGpu (total tok/s/gpu, DEFAULT), y_outputTputPerGpu, y_inputTputPerGpu, y_outputTputPerTotalGpu (output tok/s over total GPUs incl. prefill)
Efficiency: y_tpPerMw (tok/s/MW), y_outputTputPerTotalMw (output tok/s/MW over total GPUs)
Cost: y_costh (hyperscaler $/Mtok), y_costn (neocloud), y_costr (3yr rental)
Energy: y_jTotal (J/tok), y_jOutput, y_jInput
**Eval metric**: eval_score
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ function rowToLightweightPoint(row: BenchmarkRow): InferenceData | null {
const specs = getGpuSpecs(hwKey);
const power = specs.power;

// Output throughput normalized over the total GPU count — mirrors the
// outputTputPerTotalGpu derivation in createChartDataPoint (chart-utils.ts).
const totalGpuCount = entry.num_prefill_gpu + entry.num_decode_gpu;
const outputTputTotal =
entry.disagg && totalGpuCount > 0
? (outputTput * entry.num_decode_gpu) / totalGpuCount
: outputTput;

const tokPerHr = (tput * 3600) / 1_000_000;
const outTokPerHr = (outputTput * 3600) / 1_000_000;
const inTokPerHr = (inputTput * 3600) / 1_000_000;
Expand All @@ -55,8 +63,10 @@ function rowToLightweightPoint(row: BenchmarkRow): InferenceData | null {
date: row.date,
tpPerGpu: wrapMetric(tput),
outputTputPerGpu: wrapMetric(outputTput),
outputTputPerTotalGpu: wrapMetric(outputTputTotal),
inputTputPerGpu: wrapMetric(inputTput),
tpPerMw: wrapMetric(power > 0 ? (tput * 1000) / power : 0),
outputTputPerTotalMw: wrapMetric(power > 0 ? (outputTputTotal * 1000) / power : 0),
// Cost per million tokens (total / output / input)
costh: wrapMetric(tokPerHr ? specs.costh / tokPerHr : 0),
costn: wrapMetric(tokPerHr ? specs.costn / tokPerHr : 0),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
"y_outputTputPerGpu_title": "Output Token Throughput per GPU",
"y_outputTputPerGpu_titleZh": "每 GPU 输出 token 吞吐量",
"y_outputTputPerGpu_roofline": "upper_left",
"y_outputTputPerTotalGpu": "outputTputPerTotalGpu.y",
"y_outputTputPerTotalGpu_label": "Output Token Throughput per Total GPUs (tok/s/gpu)",
"y_outputTputPerTotalGpu_labelZh": "每 GPU 输出 token 吞吐量(按总 GPU 数)(tok/s/gpu)",
"y_outputTputPerTotalGpu_title": "Output Token Throughput per Total GPUs",
"y_outputTputPerTotalGpu_titleZh": "每 GPU 输出 token 吞吐量(按总 GPU 数)",
"y_outputTputPerTotalGpu_roofline": "upper_left",
"y_tpPerMw": "tpPerMw.y",
"y_tpPerMw_label": "Token Throughput per All in Utility MW (tok/s/MW)",
"y_tpPerMw_labelZh": "每全电源配置兆瓦 token 吞吐量(tok/s/MW)",
Expand All @@ -44,6 +50,12 @@
"y_outputTputPerMw_title": "Output Token Throughput per All in Utility MW",
"y_outputTputPerMw_titleZh": "每全电源配置兆瓦输出 token 吞吐量",
"y_outputTputPerMw_roofline": "upper_left",
"y_outputTputPerTotalMw": "outputTputPerTotalMw.y",
"y_outputTputPerTotalMw_label": "Output Token Throughput per All in Utility MW, Total GPUs (tok/s/MW)",
"y_outputTputPerTotalMw_labelZh": "每全电源配置兆瓦输出 token 吞吐量(按总 GPU 数)(tok/s/MW)",
"y_outputTputPerTotalMw_title": "Output Token Throughput per All in Utility MW (Total GPUs)",
"y_outputTputPerTotalMw_titleZh": "每全电源配置兆瓦输出 token 吞吐量(按总 GPU 数)",
"y_outputTputPerTotalMw_roofline": "upper_left",
"y_costh": "costh.y",
"y_costh_label": "Cost per Million Total Tokens ($)",
"y_costh_labelZh": "每百万总 token 成本($)",
Expand Down Expand Up @@ -190,6 +202,12 @@
"y_outputTputPerGpu_title": "Output Token Throughput per GPU",
"y_outputTputPerGpu_titleZh": "每 GPU 输出 token 吞吐量",
"y_outputTputPerGpu_roofline": "upper_right",
"y_outputTputPerTotalGpu": "outputTputPerTotalGpu.y",
"y_outputTputPerTotalGpu_label": "Output Token Throughput per Total GPUs (tok/s/gpu)",
"y_outputTputPerTotalGpu_labelZh": "每 GPU 输出 token 吞吐量(按总 GPU 数)(tok/s/gpu)",
"y_outputTputPerTotalGpu_title": "Output Token Throughput per Total GPUs",
"y_outputTputPerTotalGpu_titleZh": "每 GPU 输出 token 吞吐量(按总 GPU 数)",
"y_outputTputPerTotalGpu_roofline": "upper_right",
"y_tpPerMw": "tpPerMw.y",
"y_tpPerMw_label": "Token Throughput per All in Utility MW (tok/s/MW)",
"y_tpPerMw_labelZh": "每全电源配置兆瓦 token 吞吐量(tok/s/MW)",
Expand All @@ -208,6 +226,12 @@
"y_outputTputPerMw_title": "Output Token Throughput per All in Utility MW",
"y_outputTputPerMw_titleZh": "每全电源配置兆瓦输出 token 吞吐量",
"y_outputTputPerMw_roofline": "upper_right",
"y_outputTputPerTotalMw": "outputTputPerTotalMw.y",
"y_outputTputPerTotalMw_label": "Output Token Throughput per All in Utility MW, Total GPUs (tok/s/MW)",
"y_outputTputPerTotalMw_labelZh": "每全电源配置兆瓦输出 token 吞吐量(按总 GPU 数)(tok/s/MW)",
"y_outputTputPerTotalMw_title": "Output Token Throughput per All in Utility MW (Total GPUs)",
"y_outputTputPerTotalMw_titleZh": "每全电源配置兆瓦输出 token 吞吐量(按总 GPU 数)",
"y_outputTputPerTotalMw_roofline": "upper_right",
"y_costh": "costh.y",
"y_costh_label": "Cost per Million Total Tokens ($)",
"y_costh_labelZh": "每百万总 token 成本($)",
Expand Down
12 changes: 12 additions & 0 deletions packages/app/src/components/inference/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,12 @@ export interface InferenceData extends Partial<Omit<AggDataEntry, AggDataConflic
// Roofline metric fields
tpPerGpu: { y: number; roof: boolean };
outputTputPerGpu?: { y: number; roof: boolean };
outputTputPerTotalGpu?: { y: number; roof: boolean };
inputTputPerGpu?: { y: number; roof: boolean };
tpPerMw: { y: number; roof: boolean };
inputTputPerMw?: { y: number; roof: boolean };
outputTputPerMw?: { y: number; roof: boolean };
outputTputPerTotalMw?: { y: number; roof: boolean };
costh: { y: number; roof: boolean };
costn: { y: number; roof: boolean };
costr: { y: number; roof: boolean };
Expand Down Expand Up @@ -312,10 +314,12 @@ export interface InferenceData extends Partial<Omit<AggDataEntry, AggDataConflic
export type YAxisMetricKey =
| 'tpPerGpu'
| 'outputTputPerGpu'
| 'outputTputPerTotalGpu'
| 'inputTputPerGpu'
| 'tpPerMw'
| 'inputTputPerMw'
| 'outputTputPerMw'
| 'outputTputPerTotalMw'
| 'costh'
| 'costn'
| 'costr'
Expand Down Expand Up @@ -366,6 +370,10 @@ export interface ChartDefinition {
y_outputTputPerGpu_label?: string;
y_outputTputPerGpu_title?: string;
y_outputTputPerGpu_roofline?: 'upper_right' | 'upper_left' | 'lower_left' | 'lower_right';
y_outputTputPerTotalGpu?: string;
y_outputTputPerTotalGpu_label?: string;
y_outputTputPerTotalGpu_title?: string;
y_outputTputPerTotalGpu_roofline?: 'upper_right' | 'upper_left' | 'lower_left' | 'lower_right';
y_inputTputPerGpu?: string;
y_inputTputPerGpu_label?: string;
y_inputTputPerGpu_title?: string;
Expand All @@ -385,6 +393,10 @@ export interface ChartDefinition {
y_outputTputPerMw_label?: string;
y_outputTputPerMw_title?: string;
y_outputTputPerMw_roofline?: 'upper_right' | 'upper_left' | 'lower_left' | 'lower_right';
y_outputTputPerTotalMw?: string;
y_outputTputPerTotalMw_label?: string;
y_outputTputPerTotalMw_title?: string;
y_outputTputPerTotalMw_roofline?: 'upper_right' | 'upper_left' | 'lower_left' | 'lower_right';
y_costh?: string;
y_costh_label?: string;
y_costh_title?: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/components/inference/ui/ChartControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,11 @@ const METRIC_GROUPS: {
'y_tpPerGpu',
'y_inputTputPerGpu',
'y_outputTputPerGpu',
'y_outputTputPerTotalGpu',
'y_tpPerMw',
'y_inputTputPerMw',
'y_outputTputPerMw',
'y_outputTputPerTotalMw',
],
},
{
Expand Down
25 changes: 25 additions & 0 deletions packages/app/src/components/inference/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,31 @@ describe('processOverlayChartData', () => {
expect(result[0].y).toBe(5);
});

it('remaps y to outputTputPerTotalGpu for unofficial-run overlay points', () => {
// Overlay points flow through the same transform pipeline, so the total-GPU
// metrics must resolve for ?unofficialrun= overlays like any other metric.
const data = [pt({ outputTputPerTotalGpu: { y: 300, roof: false }, median_intvty: 10 } as any)];
const result = processOverlayChartData(data, 'interactivity', 'y_outputTputPerTotalGpu', null);
expect(result).toHaveLength(1);
expect(result[0].y).toBe(300);
});

it('remaps y to outputTputPerTotalMw and drops overlay points missing it', () => {
const withMetric = pt({
outputTputPerTotalMw: { y: 425, roof: false },
median_intvty: 10,
} as any);
const withoutMetric = pt({ median_intvty: 10 } as any);
const result = processOverlayChartData(
[withMetric, withoutMetric],
'interactivity',
'y_outputTputPerTotalMw',
null,
);
expect(result).toHaveLength(1);
expect(result[0].y).toBe(425);
});

it('remaps x to config override for input metrics on interactivity chart', () => {
// inputTputPerGpu has x override to p90_ttft on interactivity chart
const data = [
Expand Down
37 changes: 37 additions & 0 deletions packages/app/src/components/ui/chart-display-helpers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,41 @@ describe('MetricAssumptionNotes', () => {
expect(getVisibleCaveatText()).toContain('calculate Joules per decode GPU or per prefill GPU');
expect(getVisibleCaveatText()).toContain('direct Joules per token comparison');
});

it('shows power source badges without the per-pool disagg caveat for y_outputTputPerTotalMw', () => {
renderUi(<MetricAssumptionNotes selectedYAxisMetric="y_outputTputPerTotalMw" />);

// Power badges apply — the metric divides by the spec-sheet all-in power…
expect(getVisibleText()).toContain('All in Power/GPU:');
expect(getVisibleText()).toContain('SemiAnalysis Datacenter Industry Model');
// …but the per-decode/prefill-pool caveat does NOT: this metric already
// normalizes over the total GPU count.
expect(getVisibleCaveatText()).not.toContain(
'calculate power per decode GPU or per prefill GPU',
);
expect(getVisibleCaveatText()).toContain(
'normalizes output throughput over the total GPU count',
);
});

it('shows the total-GPU normalization note for y_outputTputPerTotalGpu only', () => {
renderUi(<MetricAssumptionNotes selectedYAxisMetric="y_outputTputPerTotalGpu" />);

expect(getVisibleCaveatText()).toContain(
'normalizes output throughput over the total GPU count',
);
// The per-decode-GPU caveat belongs to y_outputTputPerGpu, not this metric
expect(getVisibleCaveatText()).not.toContain(
'calculate output throughput per decode GPU or per prefill GPU',
);

renderUi(<MetricAssumptionNotes selectedYAxisMetric="y_outputTputPerGpu" />);

expect(getVisibleCaveatText()).toContain(
'calculate output throughput per decode GPU or per prefill GPU',
);
expect(getVisibleCaveatText()).not.toContain(
'normalizes output throughput over the total GPU count',
);
});
});
48 changes: 46 additions & 2 deletions packages/app/src/components/ui/chart-display-helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,19 @@ import type { Locale } from '@/lib/i18n';

// Keep these metric-key groups in sync with chart-utils/chart configs when new source-backed
// metrics are added; this helper owns which caption notes and caveats appear for each family.
const POWER_SOURCE_METRICS = new Set(['y_tpPerMw', 'y_inputTputPerMw', 'y_outputTputPerMw']);
const POWER_SOURCE_METRICS = new Set([
'y_tpPerMw',
'y_inputTputPerMw',
'y_outputTputPerMw',
'y_outputTputPerTotalMw',
]);
// Per-MW metrics that normalize disagg configs per decode/prefill GPU pool only.
// y_outputTputPerTotalMw is excluded — it already folds in the total GPU count,
// so the per-pool disagg caveat doesn't apply (it gets TotalGpuNote instead).
const PER_POOL_POWER_METRICS = new Set(['y_tpPerMw', 'y_inputTputPerMw', 'y_outputTputPerMw']);
// Metrics normalized over the total GPU count (prefill + decode) — disagg configs
// ARE apples-to-apples with aggregated configs here, which TotalGpuNote calls out.
const TOTAL_GPU_NORMALIZED_METRICS = new Set(['y_outputTputPerTotalGpu', 'y_outputTputPerTotalMw']);
const TOTAL_COST_METRICS = new Set(['y_costh', 'y_costn', 'y_costr']);
const OUTPUT_COST_METRICS = new Set(['y_costhOutput', 'y_costnOutput', 'y_costrOutput']);
const INPUT_COST_METRICS = new Set(['y_costhi', 'y_costni', 'y_costri']);
Expand Down Expand Up @@ -113,6 +125,34 @@ function DisaggCaveat({
);
}

function TotalGpuNote({ visible, locale = 'en' }: { visible: boolean; locale?: Locale }) {
const content =
locale === 'zh' ? (
<>
<strong>注意:</strong>该指标按总 GPU 数(预填充 + 解码 GPU)对输出吞吐量进行归一化,
因此分离式推理配置可与聚合配置直接对比。
</>
) : (
<>
<strong>Note:</strong> This metric normalizes output throughput over the total GPU count
(prefill + decode GPUs), so disaggregated inference configurations are directly comparable
with aggregated configs.
</>
);

return (
<div
className={`overflow-hidden transition-all duration-200 ease-in-out ${
visible ? 'max-h-20 opacity-100' : 'max-h-0 opacity-0'
}`}
>
<p className="text-muted-foreground text-xs mt-2 border-l-2 border-amber-500 pl-2 bg-amber-500/5 py-1">
{content}
</p>
</div>
);
}

function getCostValues(selectedYAxisMetric: string) {
return Object.fromEntries(
Object.entries(HW_REGISTRY).map(([base, specs]) => [
Expand Down Expand Up @@ -203,11 +243,15 @@ export function MetricAssumptionNotes({
/>
{includePowerThroughputCaveat && (
<DisaggCaveat
visible={POWER_SOURCE_METRICS.has(selectedYAxisMetric)}
visible={PER_POOL_POWER_METRICS.has(selectedYAxisMetric)}
calculationNoun="power"
locale={locale}
/>
)}
<TotalGpuNote
visible={TOTAL_GPU_NORMALIZED_METRICS.has(selectedYAxisMetric)}
locale={locale}
/>
{showJouleSource && (
<>
<MetricBadges label={powerLabel} values={POWER_VALUES} />
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/hooks/api/use-ai-chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,10 @@ function buildReliabilityBarData(
const METRIC_LABELS: Record<string, string> = {
y_tpPerGpu: 'Throughput/GPU',
y_outputTputPerGpu: 'Output Tput/GPU',
y_outputTputPerTotalGpu: 'Output Tput/Total GPUs',
y_inputTputPerGpu: 'Input Tput/GPU',
y_tpPerMw: 'Tput/MW',
y_outputTputPerTotalMw: 'Output Tput/MW (Total GPUs)',
y_costh: 'Cost (Hyper)',
y_costn: 'Cost (Neo)',
y_costr: 'Cost (Rental)',
Expand Down
45 changes: 45 additions & 0 deletions packages/app/src/lib/benchmark-transform.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -762,6 +762,51 @@ describe('transformBenchmarkRows — data point values', () => {
expect(point.inputTputPerGpu!.y).toBe(80);
});

it('sets outputTputPerTotalGpu equal to outputTputPerGpu for non-disagg rows', () => {
const rows = [
makeRow({
disagg: false,
metrics: {
tput_per_gpu: 500,
output_tput_per_gpu: 420,
input_tput_per_gpu: 80,
median_intvty: 18,
median_e2el: 2.5,
},
}),
];
const { chartData } = transformBenchmarkRows(rows);
const point = chartData[0][0];
expect(point.outputTputPerTotalGpu).toBeDefined();
expect(point.outputTputPerTotalGpu!.y).toBe(420);
expect(point.outputTputPerTotalMw).toBeDefined();
expect(point.outputTputPerTotalMw!.y).toBeGreaterThan(0);
});

it('normalizes outputTputPerTotalGpu over prefill+decode GPUs for disagg rows', () => {
const rows = [
makeRow({
disagg: true,
num_prefill_gpu: 4,
num_decode_gpu: 2,
metrics: {
tput_per_gpu: 500,
output_tput_per_gpu: 900,
input_tput_per_gpu: 80,
median_intvty: 18,
median_e2el: 2.5,
},
}),
];
const { chartData } = transformBenchmarkRows(rows);
const point = chartData[0][0];
// (900 tok/s/decode-gpu * 2 decode GPUs) / (4 + 2 total GPUs)
expect(point.outputTputPerTotalGpu).toBeDefined();
expect(point.outputTputPerTotalGpu!.y).toBe(300);
// Per-decode-GPU metric stays untouched
expect(point.outputTputPerGpu!.y).toBe(900);
});

it('groups data points by hwKey within each chart', () => {
const rows = [
makeRow({ hardware: 'h200', framework: 'trt', conc: 16 }),
Expand Down
Loading
Loading