Skip to content

Commit c40c77b

Browse files
authored
fix(inference): align unofficial-run overlay x-axis + roofline with official chart / 对齐非官方运行叠加层的 x 轴与 roofline (#596)
* fix(inference): apply selected percentile to unofficial-run overlay x-axis The `?unofficialrun=` overlay path (`processOverlayChartData`) never applied the selected latency percentile to the x-axis field, so on agentic charts it plotted overlay points against the raw `median_intvty` / `median_e2el` column while the official run's points sat on the percentile-adjusted column (`p90_intvty` etc.) selected via the percentile picker. Because `p90_intvty = 1/p90_itl < median_intvty = 1/median_itl`, an overlay of the *same* run rendered shifted to the right of its own official points on the "P90 Interactivity" chart (identical throughput + config labels, mismatched x). Mirror `useChartData`'s percentile handling in `processOverlayChartData`: the natural x is now `withPercentile(chartDef.x, isAgentic ? selectedPercentile : 'median')`, plus the final agentic rewrite that carries the percentile onto the input-metric override. Non-agentic (fixed-seq) stays forced to `median` (those rows carry no p90_/p99_ columns). Thread `selectedPercentile` through the `ChartDisplay` caller and its memo deps. The parallel helper `buildReplayTimeline.resolveXAxisField` has the same shape of divergence but is inert: replay only runs for fixed-seq (history is keyed by isl/osl, disabled for agentic), so its natural x is always `median`. Left unchanged; documented so it isn't mistaken for a live bug. 中文:修复非官方运行叠加层(`?unofficialrun=`)在 agentic 图表上 x 轴未应用所选延迟 百分位(percentile)的问题。此前叠加层路径 `processOverlayChartData` 始终使用原始的 `median_intvty` / `median_e2el` 列,而官方运行的数据点使用百分位选择器选定的 `p90_intvty` 等列。由于 `p90_intvty = 1/p90_itl < median_intvty = 1/median_itl`, 同一次运行的叠加层会相对其自身官方数据点向右偏移(吞吐量与配置标签相同、x 值不一致), 即用户报告的 GLM5.2 B300 "P90 Interactivity" 图表错位。现让 `processOverlayChartData` 与 `useChartData` 的百分位逻辑保持一致:自然 x 轴取 `withPercentile(chartDef.x, isAgentic ? selectedPercentile : 'median')`,并在 agentic 下对输入指标(input metric) 覆盖项再套用百分位;非 agentic(固定序列)仍强制 `median`(这些行没有 p90_/p99_ 列)。 同时将 `selectedPercentile` 传入 `ChartDisplay` 调用点及其 memo 依赖。并行的 `buildReplayTimeline.resolveXAxisField` 存在同类差异但无实际影响:replay 仅对固定序列 运行(历史数据按 isl/osl 键控,agentic 时禁用),其自然 x 轴恒为 `median`,故保持不变并加注说明。 * fix(inference): restrict unofficial-run overlay roofline to e2e-Pareto winners On the agentic interactivity chart the official roofline is restricted to configs that ALSO win on end-to-end latency (anti-benchmark-hacking): useChartData stamps `isOnE2eFrontier` and ScatterGraph's `rooflines` narrows to those winners before paretoing on the interactivity axis. The overlay path did neither — `overlayRooflines` computed a fresh frontier directly on the (interactivity, y) plane — so an `?unofficialrun=` overlay of the same run drew a line that rode ABOVE the official green line wherever an interactivity-optimal config was not e2e-optimal. - Extract the e2e-frontier seed into a shared helper `e2eFrontierWinners` (components/inference/utils/e2eFrontier.ts). `useChartData.e2eParetoIds` now derives its persisted-id set from it, so official and overlay seed identically. - `processOverlayChartData` stamps `isOnE2eFrontier` on agentic interactivity overlay points, seeded per unofficial run (matching overlayRooflines' per-run grouping) so runs don't cross-dominate. The e2e chart and fixed-seq leave the flag unset (no restriction), as on the official path. - `overlayRooflines` now honors the flag with the same narrow-to-winners logic as the official `rooflines`. Depends on the selectedPercentile plumbing from the previous commit (the e2e-latency seed field is percentile-prefixed, e.g. p90_e2el). 中文:修复非官方运行叠加层(`?unofficialrun=`)的 roofline 未套用 E2E 帕累托(Pareto) 前沿约束的问题。在 agentic 的 interactivity 图表上,官方 roofline 会限制为同时在端到端 延迟(end-to-end latency)上取胜的配置(反 benchmark 作弊):`useChartData` 为数据点打上 `isOnE2eFrontier` 标记,`ScatterGraph` 的 `rooflines` 在按 interactivity 轴求前沿前先收窄到 这些优胜点。叠加层路径两者皆无——`overlayRooflines` 直接在 (interactivity, y) 平面上重新 计算前沿,导致同一次运行的叠加层在“interactivity 最优但非 E2E 最优”的区段画出的线高于官方 绿线。改动:将 E2E 前沿种子逻辑抽取为共享函数 `e2eFrontierWinners` (components/inference/utils/e2eFrontier.ts),`useChartData.e2eParetoIds` 由其派生持久化 id 集合,使官方与叠加层种子一致;`processOverlayChartData` 为 agentic interactivity 叠加层 数据点打上 `isOnE2eFrontier`,并按每个非官方运行分别求种子(与 `overlayRooflines` 的按运行 分组一致),避免不同运行相互支配;E2E 图表与固定序列不设该标记(不做约束),与官方路径一致; `overlayRooflines` 现按与官方 `rooflines` 相同的收窄逻辑处理该标记。依赖上一提交引入的 selectedPercentile 传递(E2E 延迟种子字段带百分位前缀,如 p90_e2el)。 * fix(inference): extend overlay e2e-frontier restriction to the ttft x-mode The official path stamps `isOnE2eFrontier` for EVERY non-e2e x-axis mode (`selectedXAxisMode !== 'e2e'`), which includes the 'ttft' mode — the e2e chartType rendered with a *_ttft x override. The overlay stamping added in the previous commit only covered the interactivity chartType, so the TTFT chart's overlay roofline still drew a fresh TTFT-plane frontier instead of the e2e-restricted one. Stamp whenever the agentic chart's resolved x-axis is not the natural e2e latency (interactivity chartType, or e2e chartType with the TTFT override), matching the official condition mode-for-mode. Verified end-to-end in a real browser (Cypress against the running app, real run-29682242847 values served as both official and overlay data): official and overlay interactivity rooflines both draw 4 vertices (C=48, C=8, C=2, C=1), excluding the e2e-dominated TP8 C=4. 中文:将叠加层的 E2E 前沿约束扩展到 ttft x 轴模式。官方路径对所有非 e2e 的 x 轴模式 (`selectedXAxisMode !== 'e2e'`)都会打上 `isOnE2eFrontier` 标记,其中包括 'ttft' 模式(以 *_ttft 覆盖 x 轴渲染的 e2e chartType)。上一提交新增的叠加层打标仅覆盖了 interactivity chartType,导致 TTFT 图表的叠加层 roofline 仍在 TTFT 平面上重新计算前沿, 而非使用 E2E 约束后的前沿。现在只要 agentic 图表解析出的 x 轴不是自然 e2e 延迟 (interactivity chartType,或带 TTFT 覆盖的 e2e chartType)即打标,与官方条件逐模式对齐。 已在真实浏览器中端到端验证(Cypress 驱动运行中的应用,将 run 29682242847 的真实数据同时 作为官方与叠加层数据):官方与叠加层的 interactivity roofline 均绘制 4 个顶点 (C=48、C=8、C=2、C=1),排除了在 E2E 上被支配的 TP8 C=4。 * fix(inference): overlay points respect the Optimal Only toggle Overlay (unofficial run) X markers rendered every point unconditionally, while official points are hidden by `isPointVisible` when Optimal Only is on — and Optimal Only is ON BY DEFAULT (`i_optimal !== '0'`). In the default agentic interactivity view this made an e2e-dominated overlay config read as a pareto point: the official TP8 C=4 dot was hidden, but the overlay C=4 X stayed visible sitting on the dashed roofline (the monotone spline between C=8 and C=2 passes within ~0.5% of it at the same x). On the e2e chart the same X sits far below the curve, so the artifact only showed on non-e2e axes. Mirror the official visibility rule on the overlay path: - `overlayOptimalPoints`: identity set of every point on any overlay run's drawn roofline (already e2e-restricted for agentic non-e2e modes). Frontier arrays hold the same refs as `processedOverlayData` items, so identity membership is exact and can't collide across runs. - `isOverlayPointVisible` = `!hideNonOptimal || overlayOptimalPoints.has(d)`, exposed through `interactionRef`; applied in the overlay layer render and in the decoration effect so a toggle flip restyles existing DOM without a chart rebuild (same Effect-4 path official dots use). - Scale domains (`visiblePoints`) and the legend points table exclude hidden overlay points, matching the official branch's filters. New e2e spec drives the real pipeline with the run-29682242847 values: in the default Optimal Only view exactly 4 of 5 overlay X's are visible (C=4 hidden, matching its hidden official twin), toggling off shows all 5, re-enabling hides C=4 again. Existing overlay specs (ttft-x-axis-toggle, watermark, inference-chart) still pass. 中文:修复非官方运行叠加层数据点不响应 "Optimal Only"(仅显示最优)开关的问题。 叠加层的 X 标记此前无条件渲染所有数据点,而官方数据点在开关开启时会通过 `isPointVisible` 隐藏非帕累托(Pareto)点——且该开关默认开启(`i_optimal !== '0'`)。 在默认的 agentic interactivity 视图中,这使得在 E2E 上被支配的叠加层配置看起来像 帕累托点:官方 TP8 C=4 圆点被隐藏,但叠加层的 C=4 X 标记仍然可见,且恰好落在虚线 roofline 上(C=8 与 C=2 之间的单调样条曲线在同一 x 处与其相差不到 0.5%)。在 E2E 图表上该 X 明显低于曲线,因此该现象只出现在非 E2E 轴上。现将官方可见性规则镜像到 叠加层路径:新增 `overlayOptimalPoints`(任一叠加层运行已绘制 roofline 上数据点的 恒等集合,agentic 非 E2E 模式下已受 E2E 约束);`isOverlayPointVisible` 通过 `interactionRef` 暴露,在叠加层渲染与装饰 effect 中应用(与官方数据点相同的 Effect-4 路径,开关切换无需重建图表);坐标轴域(`visiblePoints`)与图例数据点 表格同样排除被隐藏的叠加层点。新增 e2e 测试以 run 29682242847 的真实数值驱动完整 渲染管线:默认视图下 5 个叠加层 X 中恰好 4 个可见(C=4 隐藏,与其官方对应点一致), 关闭开关显示全部 5 个,重新开启后 C=4 再次隐藏。既有叠加层测试全部通过。 * refactor(inference): share x-axis resolution and e2e-frontier contract across official/overlay paths Simplification pass over the overlay-parity fixes; also fixes the Bugbot finding that direction-less metrics cleared the overlay roofline. - `e2eFrontierWinners` now returns `Set | null` — null means the y-metric declares no e2e roofline direction, i.e. no restriction applies. Previously it returned an empty set for that case and `processOverlayChartData` stamped `isOnE2eFrontier = false` on every point, seeding an EMPTY overlay frontier for the measured-power metrics (official leaves the flag unset and draws unrestricted). `e2eParetoIds` loses its duplicated chartDef/direction guard. Frontier framing now projects `{x, y, orig}` instead of spread-copying every ~70-property point and back-mapping through a Map — the pareto fns only read x/y and return input refs. - New shared `resolveXAxisField` (utils/resolveXAxisField.ts): the x-axis branch ladder (natural percentile-adjust → input-metric overrides → e2e ttft override → agentic percentile rewrite) previously lived twice, in `useChartData.stableChartDefinitions` and `processOverlayChartData`, held in sync by "must mirror" comments — the drift pattern behind three of this branch's four bugs. Both now call the resolver; labels/headings stay in useChartData keyed off the resolver's branch discriminant. - The e2e-frontier stamping gate is now caller-computed: ChartDisplay passes `restrictToE2eFrontier = isAgentic && selectedXAxisMode !== 'e2e'` — the exact official gate — instead of processOverlayChartData re-deriving it from chartType + field suffix (the reconstruction that missed the ttft mode). - New `e2eRestrictedSeed` in e2eFrontier.ts is the single interpreter of the `isOnE2eFrontier` tri-state; both `rooflines` and `overlayRooflines` call it instead of carrying verbatim copies of the narrowing dance. - `isOverlayPointVisible` hoisted above `pointsTable`/`visiblePoints` so all Optimal Only consumers call the one predicate; overlay layer render folds its two per-point predicate calls into one `.each`. Verified: 453 inference unit tests (new regression test for the direction-less metric case), 27 overlay-related Cypress e2e tests, full unit suite at the pre-existing baseline, typecheck/lint/fmt clean. 中文:对叠加层一致性修复做简化重构,并修复 Bugbot 发现的问题(无 roofline 方向的 指标会清空叠加层前沿)。`e2eFrontierWinners` 现返回 `Set | null`——null 表示所选 y 指标未声明 e2e roofline 方向,即不适用任何约束;此前该情形返回空集合,导致 `processOverlayChartData` 给所有点打上 `isOnE2eFrontier = false`,使实测功耗类指标 的叠加层前沿为空(官方路径不设标记、绘制不受限的 roofline)。前沿计算改用 `{x, y, orig}` 精简投影,不再整体展开复制约 70 个属性的点对象。新增共享的 `resolveXAxisField`:x 轴字段解析逻辑此前在 `useChartData` 与 `processOverlayChartData` 中各有一份、仅靠"必须镜像"注释保持同步(本分支四个 bug 中有三个源于此类漂移),现两条路径调用同一解析器。E2E 前沿打标条件改由调用方 (ChartDisplay)按官方口径计算并传入(`isAgentic && selectedXAxisMode !== 'e2e'`)。 新增 `e2eRestrictedSeed` 作为 `isOnE2eFrontier` 三态标记的唯一解释器,官方与叠加层 roofline memo 均调用它。`isOverlayPointVisible` 上移,所有 Optimal Only 消费方调用 同一谓词。验证:453 个单元测试(新增无方向指标回归测试)、27 个叠加层 e2e 测试、 完整单元套件与既有基线一致,typecheck/lint/fmt 全部通过。
1 parent 13230c5 commit c40c77b

8 files changed

Lines changed: 732 additions & 141 deletions

File tree

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/**
2+
* Overlay (unofficial run) points must respect the "Optimal Only" toggle the
3+
* same way official points do.
4+
*
5+
* Regression: with Optimal Only ON, official non-pareto points are hidden via
6+
* `isPointVisible`, but overlay X markers rendered every point unconditionally.
7+
* On the agentic interactivity chart this made an e2e-dominated config (TP8
8+
* C=4 in the GLM5.2 B300 hicache run) look like a pareto point: its X marker
9+
* stayed visible sitting on the dashed roofline (the monotone spline between
10+
* C=8 and C=2 passes within ~0.5% of it) while the official twin was hidden.
11+
*
12+
* Fixture values are the real run-29682242847 numbers:
13+
* conc, p90_intvty, tput_per_gpu, p90_e2el
14+
* C=4 is dominated on e2e by C=8 (12874 tok/s @ 33.1s vs 9415 @ 48.0s), so
15+
* with Optimal Only ON exactly 4 of the 5 overlay X's must stay visible.
16+
*/
17+
import { unlockAgenticGate } from '../support/e2e';
18+
19+
const DEFAULT_MODEL_DB_KEY = 'dsv4';
20+
const AGENTIC_DATE = '2026-07-19';
21+
const OVERLAY_RUN_ID = '29682242847';
22+
const OVERLAY_RUN_URL = `https://github.com/SemiAnalysisAI/InferenceX/actions/runs/${OVERLAY_RUN_ID}`;
23+
24+
const REAL_CONFIGS: [number, number, number, number][] = [
25+
[48, 10.6, 17199, 126.9],
26+
[8, 68.5, 12874, 33.1],
27+
[4, 88.3, 9415, 48], // e2e-dominated by C=8 → NOT optimal
28+
[2, 111.1, 5018, 30],
29+
[1, 130.2, 2600, 25.8],
30+
];
31+
32+
const metricsFor = (intvty: number, tput: number, e2el: number): Record<string, number> => ({
33+
median_itl: 1 / (intvty * 1.2),
34+
p90_itl: 1 / intvty,
35+
p99_itl: 1 / (intvty * 0.8),
36+
median_e2el: e2el * 0.8,
37+
p90_e2el: e2el,
38+
p99_e2el: e2el * 1.3,
39+
median_ttft: 0.5,
40+
p90_ttft: 1,
41+
p99_ttft: 2,
42+
tput_per_gpu: tput,
43+
output_tput_per_gpu: tput * 0.3,
44+
input_tput_per_gpu: tput * 0.7,
45+
});
46+
47+
let idCursor = 900000;
48+
const b300Rows = (runUrl: string | null) =>
49+
REAL_CONFIGS.map(([conc, intvty, tput, e2el]) => ({
50+
id: runUrl ? 0 : idCursor++,
51+
hardware: 'b300',
52+
framework: 'sglang',
53+
model: DEFAULT_MODEL_DB_KEY,
54+
precision: 'fp4',
55+
spec_method: 'none',
56+
disagg: false,
57+
is_multinode: false,
58+
prefill_tp: 8,
59+
decode_tp: 8,
60+
num_prefill_gpu: 8,
61+
num_decode_gpu: 8,
62+
isl: null,
63+
osl: null,
64+
conc,
65+
offload_mode: 'on',
66+
benchmark_type: 'agentic_traces',
67+
image: 'sglang:test',
68+
metrics: metricsFor(intvty, tput, e2el),
69+
workers: null,
70+
date: AGENTIC_DATE,
71+
run_url: runUrl,
72+
}));
73+
74+
const availability = [
75+
{
76+
model: DEFAULT_MODEL_DB_KEY,
77+
isl: null,
78+
osl: null,
79+
precision: 'fp4',
80+
hardware: 'b300',
81+
framework: 'sglang',
82+
spec_method: 'none',
83+
disagg: false,
84+
benchmark_type: 'agentic_traces',
85+
date: AGENTIC_DATE,
86+
},
87+
];
88+
89+
const countVisible = ($els: JQuery<HTMLElement>): number =>
90+
[...$els].filter((el) => getComputedStyle(el).opacity !== '0').length;
91+
92+
describe('Overlay points respect Optimal Only (agentic interactivity)', () => {
93+
before(() => {
94+
cy.intercept('GET', '/api/v1/availability', { body: availability }).as('availability');
95+
cy.intercept('GET', '/api/v1/benchmarks*', { body: b300Rows(null) }).as('benchmarks');
96+
cy.intercept('GET', '/api/unofficial-run*', {
97+
body: {
98+
runInfos: [
99+
{
100+
id: OVERLAY_RUN_ID,
101+
name: 'add-glm5.2-b300-agentic-hicache',
102+
branch: 'add-glm5.2-b300-agentic-hicache',
103+
sha: 'abc000',
104+
createdAt: `${AGENTIC_DATE}T00:00:00Z`,
105+
url: OVERLAY_RUN_URL,
106+
conclusion: 'success',
107+
status: 'completed',
108+
isNonMainBranch: true,
109+
},
110+
],
111+
benchmarks: b300Rows(OVERLAY_RUN_URL),
112+
evaluations: [],
113+
},
114+
}).as('unofficialRun');
115+
cy.visit(`/inference?unofficialrun=${OVERLAY_RUN_ID}&i_seq=agentic-traces&i_pctl=p90`, {
116+
onBeforeLoad(win) {
117+
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
118+
unlockAgenticGate(win);
119+
},
120+
});
121+
cy.wait('@unofficialRun');
122+
cy.get('[data-testid="chart-figure"]').should('have.length.at.least', 1);
123+
cy.get('[data-testid="x-axis-mode-interactivity"]').should(
124+
'have.attr',
125+
'aria-selected',
126+
'true',
127+
);
128+
cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').should(
129+
'have.length',
130+
REAL_CONFIGS.length,
131+
);
132+
});
133+
134+
// Optimal Only defaults ON (i_optimal !== '0') — the DEFAULT view is where
135+
// the regression lived: official C=4 hidden, overlay C=4 X still drawn.
136+
it('hides the e2e-dominated overlay point in the default Optimal Only view', () => {
137+
cy.get('#scatter-hide-non-optimal').should('have.attr', 'data-state', 'checked');
138+
// Official parity check: 4 of 5 official dots visible.
139+
cy.get('[data-testid="inference-chart-display"] svg .dot-group').then(($dots) => {
140+
expect(countVisible($dots), 'visible official points').to.eq(REAL_CONFIGS.length - 1);
141+
});
142+
// The overlay must hide its C=4 too — 4 of 5 X markers visible.
143+
cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').then(($pts) => {
144+
expect(countVisible($pts), 'visible overlay X markers').to.eq(REAL_CONFIGS.length - 1);
145+
});
146+
});
147+
148+
it('shows all overlay points when Optimal Only is turned off', () => {
149+
cy.get('#scatter-hide-non-optimal').click();
150+
cy.get('#scatter-hide-non-optimal').should('have.attr', 'data-state', 'unchecked');
151+
cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').then(($pts) => {
152+
expect(countVisible($pts), 'visible overlay X markers').to.eq(REAL_CONFIGS.length);
153+
});
154+
});
155+
156+
it('re-hides the e2e-dominated overlay point when Optimal Only is re-enabled', () => {
157+
cy.get('#scatter-hide-non-optimal').click();
158+
cy.get('#scatter-hide-non-optimal').should('have.attr', 'data-state', 'checked');
159+
cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').then(($pts) => {
160+
expect(countVisible($pts), 'visible overlay X markers').to.eq(REAL_CONFIGS.length - 1);
161+
});
162+
});
163+
});

packages/app/src/components/inference/hooks/useChartData.ts

Lines changed: 30 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,12 @@ import {
2323
getModelSortIndex,
2424
hardwareKeyMatchesAnyBase,
2525
} from '@/lib/constants';
26-
import {
27-
mergeRunScopedRows,
28-
transformBenchmarkRows,
29-
withPercentile,
30-
} from '@/lib/benchmark-transform';
26+
import { mergeRunScopedRows, transformBenchmarkRows } from '@/lib/benchmark-transform';
3127
import { Sequence, type Model } from '@/lib/data-mappings';
3228
import { isPersistedBenchmarkId } from '@/lib/benchmark-id';
3329
import { calculateCostsForGpus, calculatePowerForGpus } from '@/lib/utils';
34-
import { paretoFrontForDirection, type ParetoDirection } from '@/lib/chart-utils';
30+
import { e2eFrontierWinners } from '@/components/inference/utils/e2eFrontier';
31+
import { resolveXAxisField } from '@/components/inference/utils/resolveXAxisField';
3532
import {
3633
applyQuickFilters,
3734
computeAvailableQuickFilters,
@@ -86,39 +83,14 @@ function e2eParetoIds(
8683
selectedYAxisMetric: string,
8784
percentile: string,
8885
): Set<number> | null {
89-
const e2eChartDef = (chartDefinitions as ChartDefinition[]).find((c) => c.chartType === 'e2e');
90-
if (!e2eChartDef) return null;
91-
const dir = e2eChartDef[`${selectedYAxisMetric}_roofline` as keyof ChartDefinition] as
92-
| ParetoDirection
93-
| undefined;
94-
if (!dir) return null;
95-
const frontierFn = paretoFrontForDirection(dir);
96-
// Percentile-prefixed e2e-latency field name (e.g. 'p90_e2el').
97-
const e2elField = withPercentile('median_e2el', percentile);
98-
const metricKey = selectedYAxisMetric.replace('y_', '') as YAxisMetricKey;
99-
100-
// Re-frame each candidate point in (e2el, y) space, then compute the
101-
// pareto per (hwKey, precision, date) bucket — frontiers don't span dates
102-
// (a May 17 point can't dominate a May 15 plot).
103-
const byGroup = new Map<string, InferenceData[]>();
104-
for (const p of points) {
105-
const yValue = (p[metricKey] as { y?: number } | undefined)?.y;
106-
const xValue = (p as unknown as Record<string, unknown>)[e2elField];
107-
if (typeof xValue !== 'number' || !Number.isFinite(xValue)) continue;
108-
if (typeof yValue !== 'number' || !Number.isFinite(yValue)) continue;
109-
const key = `${p.hwKey}|${p.precision}|${p.date}`;
110-
let bucket = byGroup.get(key);
111-
if (!bucket) {
112-
bucket = [];
113-
byGroup.set(key, bucket);
114-
}
115-
bucket.push({ ...p, x: xValue, y: yValue });
116-
}
86+
// Shared seed with the overlay path (processOverlayChartData) so both draw
87+
// the SAME e2e-restricted frontier. null = the y-metric has no e2e roofline
88+
// direction → caller skips filtering. Only persisted DB rows carry ids to pin.
89+
const winners = e2eFrontierWinners(points, selectedYAxisMetric, percentile);
90+
if (winners === null) return null;
11791
const ids = new Set<number>();
118-
for (const bucket of byGroup.values()) {
119-
for (const f of frontierFn(bucket)) {
120-
if (isPersistedBenchmarkId(f.id)) ids.add(f.id);
121-
}
92+
for (const winner of winners) {
93+
if (isPersistedBenchmarkId(winner.id)) ids.add(winner.id);
12294
}
12395
return ids;
12496
}
@@ -399,81 +371,49 @@ export function useChartData(
399371
(chartDefinitions as ChartDefinition[]).map((chartDef) => {
400372
const metricKey = selectedYAxisMetric.replace('y_', '') as YAxisMetricKey;
401373

402-
// Default x-axis = chart's natural latency metric, percentile-adjusted
403-
// for the agentic case (median_e2el → p99_e2el etc.). Percentiles only
404-
// exist for agentic scenarios; fixed-seq benchmarks have no p90_/p99_
405-
// columns, and the percentile selector is hidden for them — but its
406-
// state persists at the 'p90' default across mode/sequence switches.
407-
// Applying that stale percentile to a fixed-seq metric yields a null
408-
// column (e.g. p90_intvty), which renders as x=0/NaN and drops the
409-
// point from the chart. Force median for non-agentic so the natural
410-
// metric (median_intvty / median_e2el) is used.
374+
// Resolve which data field the x-axis plots — shared with the overlay
375+
// path (processOverlayChartData) via resolveXAxisField so the two
376+
// can't drift. Labels/headings stay here (display-only) and follow the
377+
// resolver's branch discriminant.
411378
const isAgentic = selectedSequence === Sequence.AgenticTraces;
412-
const naturalX = withPercentile(
413-
chartDef.x,
414-
isAgentic ? selectedPercentile : 'median',
415-
) as keyof AggDataEntry;
416-
let xAxisField: keyof AggDataEntry = naturalX;
417-
let xAxisLabel = chartDef.x_label;
418-
419-
const metricTitle =
420-
(chartDef[`${selectedYAxisMetric}_title` as keyof ChartDefinition] as string) || '';
421-
const isInputMetric = metricTitle.toLowerCase().includes('input');
422-
423-
// Resolve the effective x-axis override per chart type
424379
const effectiveXMetric =
425380
chartDef.chartType === 'e2e' ? selectedE2eXAxisMetric : selectedXAxisMetric;
426-
// The TTFT override is now any *_ttft metric (not just p90_ttft) — the
427-
// x-axis-mode picker reconciles the percentile prefix based on sequence
428-
// kind (fixed-seq → median, agentic → user-picked percentile).
429-
const isTtftOverride =
430-
typeof effectiveXMetric === 'string' && effectiveXMetric.endsWith('_ttft');
381+
const resolved = resolveXAxisField(chartDef, selectedYAxisMetric, effectiveXMetric, {
382+
isAgentic,
383+
percentile: selectedPercentile,
384+
});
385+
const naturalX = resolved.naturalX as keyof AggDataEntry;
386+
const xAxisField = resolved.xAxisField as keyof AggDataEntry;
387+
const { isTtftOverride } = resolved;
388+
431389
const ttftPctl = isTtftOverride
432390
? (effectiveXMetric as string).replace(/_ttft$/u, '')
433391
: 'p90';
434392
const ttftPctlWord = ttftPctl === 'median' ? 'Median' : ttftPctl.toUpperCase();
435393
const ttftLabel = `${ttftPctlWord} Time To First Token (s)`;
436394

437-
if (
438-
effectiveXMetric &&
439-
chartDef.chartType === 'interactivity' &&
440-
isInputMetric &&
441-
!isAgentic
442-
) {
443-
xAxisField = effectiveXMetric as keyof AggDataEntry;
395+
let xAxisLabel = chartDef.x_label;
396+
if (resolved.branch === 'user-input-override') {
444397
const labelKey = `${selectedYAxisMetric}_x_label` as keyof ChartDefinition;
445398
if (effectiveXMetric === chartDef[`${selectedYAxisMetric}_x` as keyof ChartDefinition]) {
446399
xAxisLabel = (chartDef[labelKey] as string) || chartDef.x_label;
447400
} else {
448401
xAxisLabel = isTtftOverride ? ttftLabel : chartDef.x_label;
449402
}
450-
} else if (chartDef.chartType === 'interactivity' && isInputMetric) {
451-
// Agentic falls through here too — the manual X-axis dropdown is
452-
// hidden in agentic mode (would double up with the percentile
453-
// selector), so the config default + percentile post-processing
454-
// below drives the x axis.
455-
const xOverrideKey = `${selectedYAxisMetric}_x` as keyof ChartDefinition;
403+
} else if (resolved.branch === 'config-input-override') {
456404
const xLabelOverrideKey = `${selectedYAxisMetric}_x_label` as keyof ChartDefinition;
457-
xAxisField = (chartDef[xOverrideKey] as keyof AggDataEntry) || chartDef.x;
458405
xAxisLabel = (chartDef[xLabelOverrideKey] as string) || chartDef.x_label;
459-
} else if (chartDef.chartType === 'e2e' && isTtftOverride) {
460-
xAxisField = effectiveXMetric as keyof AggDataEntry;
406+
} else if (resolved.branch === 'e2e-ttft-override') {
461407
xAxisLabel = ttftLabel;
462408
}
463409

464-
// Agentic: rewrite the resolved x metric to the chosen percentile,
465-
// and relabel accordingly. Both have to be updated unconditionally —
466-
// xAxisField may already be percentile-adjusted (via naturalX) while
467-
// xAxisLabel still carries the raw chartDef.x_label prefix.
468-
// The chart heading ("vs. <latency>") is also rewritten to include
469-
// the percentile so the title above the plot reflects what's drawn.
410+
// Agentic: relabel to the chosen percentile (the resolver already
411+
// rewrote the field) — xAxisLabel still carries the raw chartDef
412+
// prefix. The chart heading ("vs. <latency>") is also rewritten so the
413+
// title above the plot reflects what's drawn.
470414
const headingKey = `${selectedYAxisMetric}_heading` as keyof ChartDefinition;
471415
let chartHeading = (chartDef[headingKey] as string) || chartDef.heading;
472416
if (isAgentic) {
473-
xAxisField = withPercentile(
474-
xAxisField as string,
475-
selectedPercentile,
476-
) as keyof AggDataEntry;
477417
const pctlWord = selectedPercentile.toUpperCase();
478418
xAxisLabel = xAxisLabel.replace(/^(?:Median|Mean|P75|P90|P95|P99(?:\.9)?)\b/iu, pctlWord);
479419
chartHeading = chartHeading.replace(

packages/app/src/components/inference/ui/ChartDisplay.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,19 @@ export default function ChartDisplay() {
380380
if (!rawData || rawData.data.length === 0) return null;
381381

382382
const effectiveXMetric = chartType === 'e2e' ? selectedE2eXAxisMetric : selectedXAxisMetric;
383+
const isAgentic = sequenceKind(selectedSequence) === 'agentic';
383384
const processed = processOverlayChartData(
384385
rawData.data,
385386
chartType,
386387
selectedYAxisMetric,
387388
effectiveXMetric,
388-
{ isAgentic: sequenceKind(selectedSequence) === 'agentic' },
389+
{
390+
isAgentic,
391+
selectedPercentile,
392+
// Same gate useChartData applies to the official points — on any
393+
// non-e2e x-mode, agentic rooflines are restricted to e2e winners.
394+
restrictToE2eFrontier: isAgentic && selectedXAxisMode !== 'e2e',
395+
},
389396
);
390397

391398
let overlayPoints = processed;
@@ -425,6 +432,8 @@ export default function ChartDisplay() {
425432
selectedYAxisMetric,
426433
selectedXAxisMetric,
427434
selectedE2eXAxisMetric,
435+
selectedPercentile,
436+
selectedXAxisMode,
428437
compareGpuPair,
429438
]);
430439

0 commit comments

Comments
 (0)