Skip to content

Commit d676b07

Browse files
committed
fix: orient power envelopes by frontier direction, not chart type
中文:功耗包络线按前沿方向而非图表类型确定起点,修复 E2E Normalized Interactivity 横轴下包络线方向错误
1 parent 53cc1bd commit d676b07

7 files changed

Lines changed: 31 additions & 21 deletions

File tree

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -395,13 +395,13 @@ const GPUGraph = React.memo(
395395
}, [groupedData, frontierDirection]);
396396

397397
const rooflines = useMemo(() => {
398-
if (!powerEnvelopeMode) return paretoRooflines;
398+
if (!powerEnvelopeMode || !frontierDirection) return paretoRooflines;
399399
const result: Record<string, InferenceData[]> = {};
400400
for (const [key, points] of Object.entries(groupedData)) {
401-
result[key] = upperPowerEnvelope(points, chartDefinition.chartType !== 'e2e');
401+
result[key] = upperPowerEnvelope(points, frontierDirection);
402402
}
403403
return result;
404-
}, [powerEnvelopeMode, groupedData, paretoRooflines, chartDefinition.chartType]);
404+
}, [powerEnvelopeMode, groupedData, paretoRooflines, frontierDirection]);
405405

406406
const boundaryPointKeys = useMemo(() => {
407407
const keys = new Set<string>();

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ import {
100100
getShapeKeyForPrecision,
101101
} from '@/lib/chart-rendering';
102102
import { useThemeColors } from '@/hooks/useThemeColors';
103-
import { isFrontierEligible, type ParetoDirection } from '@/lib/chart-utils';
103+
import { isFrontierEligible, paretoMaximizesX, type ParetoDirection } from '@/lib/chart-utils';
104104
import { frontierHardwareKeys } from '@/components/inference/utils/pareto-series';
105105
import {
106106
globalParetoFrontier,
@@ -550,14 +550,14 @@ const ScatterGraph = React.memo(
550550
const groupDisplayedPoints = useCallback(
551551
(points: InferenceData[]) => {
552552
const groups = groupPointsByDate(points);
553-
if (showPowerEnvelope) {
553+
if (showPowerEnvelope && paretoDirection) {
554554
for (const [date, samples] of groups) {
555-
groups.set(date, upperPowerEnvelope(samples, chartDefinition.chartType !== 'e2e'));
555+
groups.set(date, upperPowerEnvelope(samples, paretoDirection));
556556
}
557557
}
558558
return groups;
559559
},
560-
[showPowerEnvelope, chartDefinition.chartType],
560+
[showPowerEnvelope, paretoDirection],
561561
);
562562
const locale = useLocale();
563563
const legendT = SCATTER_STRINGS[locale];
@@ -1501,10 +1501,7 @@ const ScatterGraph = React.memo(
15011501
],
15021502
);
15031503

1504-
// Roofline direction names are historical: upper_left maximizes X,
1505-
// upper_right minimizes X. Use the resolved definition, which also covers
1506-
// custom X metrics such as TTFT, rather than inferring from chart type.
1507-
const maximizeParetoX = paretoDirection === 'upper_left' || paretoDirection === 'lower_right';
1504+
const maximizeParetoX = paretoDirection !== undefined && paretoMaximizesX(paretoDirection);
15081505
const maximizeParetoY = paretoDirection?.startsWith('upper') ?? true;
15091506
const globalParetoPoints = useMemo(
15101507
() =>

‎packages/app/src/components/inference/utils/powerCurves.test.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ describe('upper power envelope', () => {
8686
const dominated = point(64, 20.51, 344.95, { decode_ep: 8 });
8787
const slower = point(256, 4.65, 372.9, { decode_ep: 8 });
8888
const input = Object.freeze([fast, middle, dominated, peak, slower]);
89-
expect(upperPowerEnvelope(input, true)).toEqual([peak, middle, fast]);
89+
expect(upperPowerEnvelope(input, 'lower_right')).toEqual([peak, middle, fast]);
9090
expect(input).toEqual([fast, middle, dominated, peak, slower]);
9191
expect(chartFrontier([...input], 'lower_right')).toEqual([fast]);
9292
});
@@ -96,18 +96,18 @@ describe('upper power envelope', () => {
9696
const middle = point(8, 2, 700);
9797
const slow = point(32, 4, 950);
9898
const samples = [slow, point(16, 3, 700), point(4, 2, 500), middle, { ...middle }, fast];
99-
expect(upperPowerEnvelope(samples, false)).toEqual([fast, middle, slow]);
99+
expect(upperPowerEnvelope(samples, 'lower_left')).toEqual([fast, middle, slow]);
100100
expect(
101101
upperPowerEnvelope(
102102
samples.map((p) => ({ ...p, x: 1000 / p.x })),
103-
true,
103+
'lower_right',
104104
).map((p) => p.y),
105105
).toEqual([950, 700, 350]);
106106
});
107107

108108
it('uses only finite positive coordinates and preserves singleton boundaries', () => {
109109
const valid = point(1, 200, 350);
110-
expect(upperPowerEnvelope([], true)).toEqual([]);
110+
expect(upperPowerEnvelope([], 'lower_right')).toEqual([]);
111111
expect(
112112
upperPowerEnvelope(
113113
[
@@ -120,7 +120,7 @@ describe('upper power envelope', () => {
120120
point(1, 10, -1),
121121
valid,
122122
],
123-
true,
123+
'lower_right',
124124
),
125125
).toEqual([valid]);
126126
});

‎packages/app/src/components/inference/utils/powerCurves.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { InferenceData } from '@/components/inference/types';
22
import {
33
isFrontierEligible,
44
paretoFrontForDirection,
5+
paretoMaximizesX,
56
type ParetoDirection,
67
} from '@/lib/chart-utils';
78

@@ -39,13 +40,15 @@ export function chartFrontier(
3940

4041
/**
4142
* Higher-power outer boundary across tested configurations, not an efficiency
42-
* frontier. Unique X vertices avoid concurrency backtracking during smoothing.
43+
* frontier. It starts from the X end the chart's frontier `direction` favors.
44+
* Unique X vertices avoid concurrency backtracking during smoothing.
4345
* Callers scope the samples by hardware, precision, date and overlay run.
4446
*/
4547
export function upperPowerEnvelope(
4648
points: readonly InferenceData[],
47-
maximizeX: boolean,
49+
direction: ParetoDirection,
4850
): InferenceData[] {
51+
const maximizeX = paretoMaximizesX(direction);
4952
const sorted = points
5053
.filter((point) => isFrontierEligible(point) && Number.isFinite(point.y) && point.y > 0)
5154
.sort((a, b) => (maximizeX ? b.x - a.x : a.x - b.x) || b.y - a.y);

‎packages/app/src/lib/api-route-catalog.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,7 @@ export const apiContractSourceDigests = [
931931

932932
{
933933
source: 'src/lib/views-api/series.ts',
934-
sourceSha256: 'b5ebddee9d9ea6b85a8fdab2cf8fcc50bbf05e0fa10564987698abd38a6d4488',
934+
sourceSha256: '8290913d9632434148a4b1951259492c4d5e67031b37a8ded12feb640c72bf38',
935935
reviewArea: {
936936
en: 'Dashboard read-only selector and calculation parity.',
937937
zh: '仪表板只读接口的选择项与计算一致性。',

‎packages/app/src/lib/chart-utils.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,15 @@ export const paretoFrontForDirection = (
758758
dir: ParetoDirection,
759759
): ((points: InferenceData[]) => InferenceData[]) => PARETO_BY_DIRECTION[dir];
760760

761+
/**
762+
* Whether a roofline direction favors higher X. The names are historical:
763+
* upper_left maximizes X and upper_right minimizes X. Read X direction from
764+
* the resolved corner, never from chart type: trace-derived and custom X
765+
* metrics reuse a chart definition whose natural X runs the other way.
766+
*/
767+
export const paretoMaximizesX = (dir: ParetoDirection): boolean =>
768+
dir === 'upper_left' || dir === 'lower_right';
769+
761770
// ---------------------------------------------------------------------------
762771
// Locale-aware metric label/title helpers
763772
// ---------------------------------------------------------------------------

‎packages/app/src/lib/views-api/series.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { transformBenchmarkRows } from '@/lib/benchmark-transform';
3737
import {
3838
isFrontierEligible,
3939
paretoFrontForDirection,
40+
paretoMaximizesX,
4041
remapInferencePoint,
4142
type ParetoDirection,
4243
} from '@/lib/chart-utils';
@@ -287,7 +288,7 @@ export function buildInferenceSeries(
287288
// 7. Frontier flags, scoped per (hwKey, precision, date) like ScatterGraph.
288289
// Measured power represents load demand, so retain its upper boundary.
289290
const isMeasuredPower = isMeasuredPowerCurveMetric(metricConfigKey);
290-
const maximizePowerX = chartDef.chartType !== 'e2e';
291+
const maximizePowerX = direction !== null && paretoMaximizesX(direction);
291292
const frontierDirection = isMeasuredPower
292293
? maximizePowerX
293294
? 'upper_right'
@@ -305,7 +306,7 @@ export function buildInferenceSeries(
305306
}
306307
for (const bucket of byHwDate.values()) {
307308
const frontier = isMeasuredPower
308-
? upperPowerEnvelope(bucket, maximizePowerX)
309+
? upperPowerEnvelope(bucket, direction)
309310
: frontierFn(bucket.filter(isFrontierEligible));
310311
for (const point of frontier) {
311312
frontierPoints.add(point);

0 commit comments

Comments
 (0)