Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ function rowToLightweightPoint(row: BenchmarkRow): InferenceData | null {
...(typeof entry.joules_per_total_token === 'number'
? { measuredJPerTotalToken: { y: entry.joules_per_total_token, roof: false } }
: {}),
...(typeof entry.prefill_avg_power_w === 'number'
? { measuredPrefillAvgPower: { y: entry.prefill_avg_power_w, roof: false } }
: {}),
...(typeof entry.decode_avg_power_w === 'number'
? { measuredDecodeAvgPower: { y: entry.decode_avg_power_w, roof: false } }
: {}),
...(typeof entry.joules_per_input_token === 'number'
? { measuredJPerInputToken: { y: entry.joules_per_input_token, roof: false } }
: {}),
};
return point;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,20 @@
"y_measuredAvgPower": "measuredAvgPower.y",
"y_measuredAvgPower_label": "Measured Avg Power per GPU (W)",
"y_measuredAvgPower_title": "Measured Average Power per GPU",
"y_measuredPrefillAvgPower": "measuredPrefillAvgPower.y",
"y_measuredPrefillAvgPower_label": "Measured Prefill Power per GPU (W)",
"y_measuredPrefillAvgPower_title": "Measured Prefill Power per GPU",
"y_measuredDecodeAvgPower": "measuredDecodeAvgPower.y",
"y_measuredDecodeAvgPower_label": "Measured Decode Power per GPU (W)",
"y_measuredDecodeAvgPower_title": "Measured Decode Power per GPU",
"y_measuredJPerOutputToken": "measuredJPerOutputToken.y",
"y_measuredJPerOutputToken_label": "Measured J per Output Token (J/tok)",
"y_measuredJPerOutputToken_title": "Measured Joules per Output Token",
"y_measuredJPerOutputToken_roofline": "lower_right",
"y_measuredJPerInputToken": "measuredJPerInputToken.y",
"y_measuredJPerInputToken_label": "Measured J per Input Token (J/tok)",
"y_measuredJPerInputToken_title": "Measured Joules per Input Token",
"y_measuredJPerInputToken_roofline": "lower_right",
"y_measuredJPerTotalToken": "measuredJPerTotalToken.y",
"y_measuredJPerTotalToken_label": "Measured J per Token (J/tok)",
"y_measuredJPerTotalToken_title": "Measured Joules per Token (incl. prompt)",
Expand Down Expand Up @@ -193,10 +203,20 @@
"y_measuredAvgPower": "measuredAvgPower.y",
"y_measuredAvgPower_label": "Measured Avg Power per GPU (W)",
"y_measuredAvgPower_title": "Measured Average Power per GPU",
"y_measuredPrefillAvgPower": "measuredPrefillAvgPower.y",
"y_measuredPrefillAvgPower_label": "Measured Prefill Power per GPU (W)",
"y_measuredPrefillAvgPower_title": "Measured Prefill Power per GPU",
"y_measuredDecodeAvgPower": "measuredDecodeAvgPower.y",
"y_measuredDecodeAvgPower_label": "Measured Decode Power per GPU (W)",
"y_measuredDecodeAvgPower_title": "Measured Decode Power per GPU",
"y_measuredJPerOutputToken": "measuredJPerOutputToken.y",
"y_measuredJPerOutputToken_label": "Measured J per Output Token (J/tok)",
"y_measuredJPerOutputToken_title": "Measured Joules per Output Token",
"y_measuredJPerOutputToken_roofline": "lower_left",
"y_measuredJPerInputToken": "measuredJPerInputToken.y",
"y_measuredJPerInputToken_label": "Measured J per Input Token (J/tok)",
"y_measuredJPerInputToken_title": "Measured Joules per Input Token",
"y_measuredJPerInputToken_roofline": "lower_left",
"y_measuredJPerTotalToken": "measuredJPerTotalToken.y",
"y_measuredJPerTotalToken_label": "Measured J per Token (J/tok)",
"y_measuredJPerTotalToken_title": "Measured Joules per Token (incl. prompt)",
Expand Down
20 changes: 19 additions & 1 deletion packages/app/src/components/inference/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,11 @@ export interface InferenceData extends Partial<Omit<AggDataEntry, AggDataConflic
// pre-aggregate_power.py runs (and runs with monitoring disabled) won't
// emit these fields.
measuredAvgPower?: { y: number; roof: boolean };
measuredPrefillAvgPower?: { y: number; roof: boolean };
measuredDecodeAvgPower?: { y: number; roof: boolean };
measuredJPerOutputToken?: { y: number; roof: boolean };
measuredJPerTotalToken?: { y: number; roof: boolean };
measuredJPerInputToken?: { y: number; roof: boolean };
}

/**
Expand Down Expand Up @@ -260,8 +263,11 @@ export type YAxisMetricKey =
| 'jOutput'
| 'jInput'
| 'measuredAvgPower'
| 'measuredPrefillAvgPower'
| 'measuredDecodeAvgPower'
| 'measuredJPerOutputToken'
| 'measuredJPerTotalToken';
| 'measuredJPerTotalToken'
| 'measuredJPerInputToken';

/**
* Defines the configuration and labels for a specific chart.
Expand Down Expand Up @@ -370,10 +376,22 @@ export interface ChartDefinition {
// The field stays in the type for parity with the other y_* metrics and
// so a future config can override the default.
y_measuredAvgPower_roofline?: 'upper_right' | 'upper_left' | 'lower_left' | 'lower_right';
y_measuredPrefillAvgPower?: string;
y_measuredPrefillAvgPower_label?: string;
y_measuredPrefillAvgPower_title?: string;
y_measuredPrefillAvgPower_roofline?: 'upper_right' | 'upper_left' | 'lower_left' | 'lower_right';
y_measuredDecodeAvgPower?: string;
y_measuredDecodeAvgPower_label?: string;
y_measuredDecodeAvgPower_title?: string;
y_measuredDecodeAvgPower_roofline?: 'upper_right' | 'upper_left' | 'lower_left' | 'lower_right';
y_measuredJPerOutputToken?: string;
y_measuredJPerOutputToken_label?: string;
y_measuredJPerOutputToken_title?: string;
y_measuredJPerOutputToken_roofline?: 'upper_right' | 'upper_left' | 'lower_left' | 'lower_right';
y_measuredJPerInputToken?: string;
y_measuredJPerInputToken_label?: string;
y_measuredJPerInputToken_title?: string;
y_measuredJPerInputToken_roofline?: 'upper_right' | 'upper_left' | 'lower_left' | 'lower_right';
y_measuredJPerTotalToken?: string;
y_measuredJPerTotalToken_label?: string;
y_measuredJPerTotalToken_title?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,14 @@ const METRIC_GROUPS: { label: string; metrics: string[]; gated?: boolean }[] = [
{ label: 'All-in Provisioned Energy per Token', metrics: ['y_jTotal', 'y_jOutput', 'y_jInput'] },
{
label: 'Measured Energy',
metrics: ['y_measuredAvgPower', 'y_measuredJPerOutputToken', 'y_measuredJPerTotalToken'],
metrics: [
'y_measuredPrefillAvgPower',
'y_measuredDecodeAvgPower',
'y_measuredAvgPower',
'y_measuredJPerInputToken',
'y_measuredJPerOutputToken',
'y_measuredJPerTotalToken',
],
gated: true,
},
{ label: 'Custom User Values', metrics: ['y_costUser', 'y_powerUser'] },
Expand Down
73 changes: 73 additions & 0 deletions packages/app/src/lib/chart-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1294,6 +1294,79 @@ describe('createChartDataPoint measured power fields', () => {
});
});

// ===========================================================================
// createChartDataPoint — per-stage measured power / energy (disagg prefill/decode)
// ===========================================================================
describe('createChartDataPoint per-stage measured power fields', () => {
it('emits measuredPrefillAvgPower when prefill_avg_power_w is present', () => {
const e = entry({ prefill_avg_power_w: 920.3 });
const point = createChartDataPoint('2025-01-01', e, 'median_e2el', 'tput_per_gpu', 'h100');
expect(point.measuredPrefillAvgPower).toBeDefined();
expect(point.measuredPrefillAvgPower!.y).toBe(920.3);
expect(point.measuredPrefillAvgPower!.roof).toBe(false);
});

it('emits measuredDecodeAvgPower when decode_avg_power_w is present', () => {
const e = entry({ decode_avg_power_w: 612.1 });
const point = createChartDataPoint('2025-01-01', e, 'median_e2el', 'tput_per_gpu', 'h100');
expect(point.measuredDecodeAvgPower).toBeDefined();
expect(point.measuredDecodeAvgPower!.y).toBe(612.1);
expect(point.measuredDecodeAvgPower!.roof).toBe(false);
});

it('emits measuredJPerInputToken when joules_per_input_token is present', () => {
const e = entry({ joules_per_input_token: 0.27 });
const point = createChartDataPoint('2025-01-01', e, 'median_e2el', 'tput_per_gpu', 'h100');
expect(point.measuredJPerInputToken).toBeDefined();
expect(point.measuredJPerInputToken!.y).toBe(0.27);
expect(point.measuredJPerInputToken!.roof).toBe(false);
});

it('omits all per-stage fields on legacy rows predating per-stage attribution', () => {
// Single-node / pre-disagg runs emit avg_power_w only, no prefill/decode split.
const e = entry({ avg_power_w: 685.5 });
const point = createChartDataPoint('2025-01-01', e, 'median_e2el', 'tput_per_gpu', 'h100');
expect(point.measuredPrefillAvgPower).toBeUndefined();
expect(point.measuredDecodeAvgPower).toBeUndefined();
expect(point.measuredJPerInputToken).toBeUndefined();
});

it('emits prefill and decode independently — the disagg per-stage split', () => {
// GB300 disagg: prefill GPUs run compute-bound (higher W) than decode GPUs.
const e = entry({ prefill_avg_power_w: 948, decode_avg_power_w: 631 });
const point = createChartDataPoint('2025-01-01', e, 'median_e2el', 'tput_per_gpu', 'h100');
expect(point.measuredPrefillAvgPower!.y).toBe(948);
expect(point.measuredDecodeAvgPower!.y).toBe(631);
expect(point.measuredPrefillAvgPower!.y).toBeGreaterThan(point.measuredDecodeAvgPower!.y);
});

it('preserves a zero per-stage power value (not falsy-coerced away)', () => {
// Same typeof===number gate as total power — 0 W must survive, not be dropped.
const e = entry({ prefill_avg_power_w: 0, decode_avg_power_w: 0 });
const point = createChartDataPoint('2025-01-01', e, 'median_e2el', 'tput_per_gpu', 'h100');
expect(point.measuredPrefillAvgPower).toBeDefined();
expect(point.measuredPrefillAvgPower!.y).toBe(0);
expect(point.measuredDecodeAvgPower).toBeDefined();
expect(point.measuredDecodeAvgPower!.y).toBe(0);
});

it('carries total and per-stage power together on a full disagg row', () => {
const e = entry({
avg_power_w: 853,
prefill_avg_power_w: 948,
decode_avg_power_w: 631,
joules_per_input_token: 0.18,
joules_per_output_token: 1.64,
});
const point = createChartDataPoint('2025-01-01', e, 'median_e2el', 'tput_per_gpu', 'h100');
expect(point.measuredAvgPower!.y).toBe(853);
expect(point.measuredPrefillAvgPower!.y).toBe(948);
expect(point.measuredDecodeAvgPower!.y).toBe(631);
expect(point.measuredJPerInputToken!.y).toBe(0.18);
expect(point.measuredJPerOutputToken!.y).toBe(1.64);
});
});

// ===========================================================================
// createChartDataPoint — boolean narrowing for prefill/decode dp_attention, is_multinode
// ===========================================================================
Expand Down
34 changes: 32 additions & 2 deletions packages/app/src/lib/chart-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,11 @@ export const Y_AXIS_METRICS = [
// Measured power / energy (sourced from runner's aggregate_power.py output;
// distinct from the spec-sheet TDP-derived jTotal/jOutput/jInput above).
'y_measuredAvgPower',
'y_measuredPrefillAvgPower',
'y_measuredDecodeAvgPower',
'y_measuredJPerOutputToken',
'y_measuredJPerTotalToken',
'y_measuredJPerInputToken',
] as const;

export type YAxisMetric = (typeof Y_AXIS_METRICS)[number];
Expand Down Expand Up @@ -401,12 +404,21 @@ export function createChartDataPoint(
...(typeof entry.avg_power_w === 'number'
? { measuredAvgPower: { y: entry.avg_power_w, roof: false } }
: {}),
...(typeof entry.prefill_avg_power_w === 'number'
? { measuredPrefillAvgPower: { y: entry.prefill_avg_power_w, roof: false } }
: {}),
...(typeof entry.decode_avg_power_w === 'number'
? { measuredDecodeAvgPower: { y: entry.decode_avg_power_w, roof: false } }
: {}),
...(typeof entry.joules_per_output_token === 'number'
? { measuredJPerOutputToken: { y: entry.joules_per_output_token, roof: false } }
: {}),
...(typeof entry.joules_per_total_token === 'number'
? { measuredJPerTotalToken: { y: entry.joules_per_total_token, roof: false } }
: {}),
...(typeof entry.joules_per_input_token === 'number'
? { measuredJPerInputToken: { y: entry.joules_per_input_token, roof: false } }
: {}),
};
}

Expand Down Expand Up @@ -569,8 +581,11 @@ export const calculateRoofline = (
| `jOutput.y`
| `jInput.y`
| `measuredAvgPower.y`
| `measuredPrefillAvgPower.y`
| `measuredDecodeAvgPower.y`
| `measuredJPerOutputToken.y`
| `measuredJPerTotalToken.y`,
| `measuredJPerTotalToken.y`
| `measuredJPerInputToken.y`,
rooflineDirection: 'upper_right' | 'upper_left' | 'lower_left' | 'lower_right',
): InferenceData[] => {
const pointsForRoofline = points.map((p) => {
Expand Down Expand Up @@ -642,8 +657,11 @@ export function computeAllRooflines(
| `jOutput.y`
| `jInput.y`
| `measuredAvgPower.y`
| `measuredPrefillAvgPower.y`
| `measuredDecodeAvgPower.y`
| `measuredJPerOutputToken.y`
| `measuredJPerTotalToken.y`,
| `measuredJPerTotalToken.y`
| `measuredJPerInputToken.y`,
rooflineDirection,
);
}
Expand Down Expand Up @@ -688,8 +706,11 @@ export function markRooflinePoints(
if (newPoint.jOutput) newPoint.jOutput.roof = false;
if (newPoint.jInput) newPoint.jInput.roof = false;
if (newPoint.measuredAvgPower) newPoint.measuredAvgPower.roof = false;
if (newPoint.measuredPrefillAvgPower) newPoint.measuredPrefillAvgPower.roof = false;
if (newPoint.measuredDecodeAvgPower) newPoint.measuredDecodeAvgPower.roof = false;
if (newPoint.measuredJPerOutputToken) newPoint.measuredJPerOutputToken.roof = false;
if (newPoint.measuredJPerTotalToken) newPoint.measuredJPerTotalToken.roof = false;
if (newPoint.measuredJPerInputToken) newPoint.measuredJPerInputToken.roof = false;

for (const chartDefYKey of Y_AXIS_METRICS) {
const rooflinePoints = computedRooflines[hwKey]?.[chartDefYKey];
Expand Down Expand Up @@ -751,13 +772,22 @@ export function markRooflinePoints(
newPoint.jInput.roof = onCurrentRoofline;
} else if (chartDefYKey === 'y_measuredAvgPower' && newPoint.measuredAvgPower) {
newPoint.measuredAvgPower.roof = onCurrentRoofline;
} else if (
chartDefYKey === 'y_measuredPrefillAvgPower' &&
newPoint.measuredPrefillAvgPower
) {
newPoint.measuredPrefillAvgPower.roof = onCurrentRoofline;
} else if (chartDefYKey === 'y_measuredDecodeAvgPower' && newPoint.measuredDecodeAvgPower) {
newPoint.measuredDecodeAvgPower.roof = onCurrentRoofline;
} else if (
chartDefYKey === 'y_measuredJPerOutputToken' &&
newPoint.measuredJPerOutputToken
) {
newPoint.measuredJPerOutputToken.roof = onCurrentRoofline;
} else if (chartDefYKey === 'y_measuredJPerTotalToken' && newPoint.measuredJPerTotalToken) {
newPoint.measuredJPerTotalToken.roof = onCurrentRoofline;
} else if (chartDefYKey === 'y_measuredJPerInputToken' && newPoint.measuredJPerInputToken) {
newPoint.measuredJPerInputToken.roof = onCurrentRoofline;
}
}
finalProcessedData.push(newPoint);
Expand Down
Loading