From dc6ef9182edf0bfcc721da3e4c6cae193e0ba27f Mon Sep 17 00:00:00 2001 From: Oseltamivir <58582368+Oseltamivir@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:26:01 +0800 Subject: [PATCH 1/2] fix(inference): make official legend X work while an unofficial overlay is loaded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With an unofficial-run overlay active, the chart reads official series visibility from `localOfficialOverride` (the unified overlay-mode selection), but the legend's "X" (remove) button routed straight to InferenceContext's `removeHwType`, which mutates `activeHwTypes` — a set the chart ignores in overlay mode. Clicking an official SKU's X therefore appeared to do nothing, while the same click worked on official-only charts. The legend row TOGGLE already had the overlay-aware split (`unifiedToggle` → commitUnifiedSelection); the X now shares it: `handleRemoveHwType` commits the removal through the unified selection when an overlay is loaded and delegates to the context's `removeHwType` otherwise. Context state stays untouched in overlay mode, so dismissing the overlay restores the pre-overlay official selection — same semantics as the toggle path. Also lifts the overlay e2e fixtures (real run-29682242847 values, intercepts, countVisible) into cypress/support/overlay-fixtures.ts, shared by the existing overlay-optimal-only spec and the new overlay-legend-remove regression spec (which fails without the fix). Assertions in both specs now use retryable `.should()` callbacks — the dots' 150ms opacity transition made one-shot `.then()` counts racy. 中文:修复加载非官方运行叠加层时,图例中官方 SKU 的"X"(移除)按钮无效的问题。 叠加层激活时图表从 `localOfficialOverride`(叠加模式的统一选择集)读取官方系列 可见性,而图例的 X 按钮直接调用 InferenceContext 的 `removeHwType` 修改 `activeHwTypes`——该集合在叠加模式下被图表忽略,因此点击官方 SKU 的 X 看似无效, 而在纯官方图表上正常。图例行的切换(toggle)已有叠加感知分支(`unifiedToggle`), 现在 X 与其共用:加载叠加层时 `handleRemoveHwType` 通过统一选择集提交移除,否则 委托给 context 的 `removeHwType`;叠加模式下不触碰 context 状态,关闭叠加层即恢复 之前的官方选择,与切换路径语义一致。同时将叠加层 e2e 测试夹具(fixtures)提取到 cypress/support/overlay-fixtures.ts,由既有 overlay-optimal-only 与新增的 overlay-legend-remove 回归测试(未修复时失败)共享;两个测试的断言改用可重试的 `.should()` 回调(数据点 150ms 的透明度过渡使一次性 `.then()` 计数存在竞态)。 --- .../cypress/e2e/overlay-legend-remove.cy.ts | 71 ++++++++++ .../cypress/e2e/overlay-optimal-only.cy.ts | 127 +++--------------- .../app/cypress/support/overlay-fixtures.ts | 108 +++++++++++++++ .../components/inference/ui/ScatterGraph.tsx | 22 ++- 4 files changed, 218 insertions(+), 110 deletions(-) create mode 100644 packages/app/cypress/e2e/overlay-legend-remove.cy.ts create mode 100644 packages/app/cypress/support/overlay-fixtures.ts diff --git a/packages/app/cypress/e2e/overlay-legend-remove.cy.ts b/packages/app/cypress/e2e/overlay-legend-remove.cy.ts new file mode 100644 index 00000000..6eea67e0 --- /dev/null +++ b/packages/app/cypress/e2e/overlay-legend-remove.cy.ts @@ -0,0 +1,71 @@ +/** + * Clicking an official SKU's legend "X" must remove that series even while an + * unofficial-run overlay is loaded. + * + * Regression: with an overlay active, the chart reads official visibility from + * `localOfficialOverride` (the unified overlay-mode selection), but the legend + * X routed straight to InferenceContext's `removeHwType`, which mutates + * `activeHwTypes` — a set the chart ignores in overlay mode. The click + * appeared to do nothing. The legend toggle already had the overlay-aware + * split (`unifiedToggle`); the X now shares it (`handleRemoveHwType`). + */ +import { unlockAgenticGate } from '../support/e2e'; +import { + countVisible, + interceptOverlayRun, + OVERLAY_RUN_ID, + REAL_CONFIGS, +} from '../support/overlay-fixtures'; + +describe('Official legend X works while an unofficial overlay is loaded', () => { + before(() => { + interceptOverlayRun(); + cy.visit(`/inference?unofficialrun=${OVERLAY_RUN_ID}&i_seq=agentic-traces&i_pctl=p90`, { + onBeforeLoad(win) { + win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now())); + unlockAgenticGate(win); + }, + }); + cy.wait('@unofficialRun'); + cy.get('[data-testid="chart-figure"]').should('have.length.at.least', 1); + cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').should( + 'have.length', + REAL_CONFIGS.length, + ); + }); + + it('shows official points and an official legend entry initially', () => { + cy.get('[data-testid="inference-chart-display"] svg .dot-group').should(($dots) => { + expect(countVisible($dots), 'visible official points').to.be.greaterThan(0); + }); + cy.get('[data-testid="chart-legend"]').contains('B300').should('exist'); + }); + + it('clicking the official SKU X hides its points but keeps the overlay', () => { + // The X only becomes opaque on row hover (CSS group-hover), which Cypress + // events don't trigger — force the click on the always-present element. + // Target the OFFICIAL row's X: the overlay run row is listed first and has + // its own (no-op) X, so `.first()` would hit the wrong one. The official + // label is "B300 (SGLang)" — case-sensitive match excludes the overlay + // row's lowercase branch name. + cy.get('[data-testid="chart-legend"] [role="button"][aria-label^="Hide"][aria-label*="B300"]') + .first() + .click({ force: true }); + + // Every official point belongs to the removed B300 series → all hidden. + cy.get('[data-testid="inference-chart-display"] svg .dot-group').should(($dots) => { + expect(countVisible($dots), 'visible official points after remove').to.eq(0); + }); + // The overlay series is untouched (Optimal Only default keeps 4 of 5). + cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').should(($pts) => { + expect(countVisible($pts), 'visible overlay X markers').to.eq(REAL_CONFIGS.length - 1); + }); + }); + + it('re-activating the SKU from the legend restores the official points', () => { + cy.get('[data-testid="chart-legend"]').contains('B300').click(); + cy.get('[data-testid="inference-chart-display"] svg .dot-group').should(($dots) => { + expect(countVisible($dots), 'visible official points after re-add').to.be.greaterThan(0); + }); + }); +}); diff --git a/packages/app/cypress/e2e/overlay-optimal-only.cy.ts b/packages/app/cypress/e2e/overlay-optimal-only.cy.ts index 68803f42..c7c7a88a 100644 --- a/packages/app/cypress/e2e/overlay-optimal-only.cy.ts +++ b/packages/app/cypress/e2e/overlay-optimal-only.cy.ts @@ -2,116 +2,25 @@ * Overlay (unofficial run) points must respect the "Optimal Only" toggle the * same way official points do. * - * Regression: with Optimal Only ON, official non-pareto points are hidden via - * `isPointVisible`, but overlay X markers rendered every point unconditionally. - * On the agentic interactivity chart this made an e2e-dominated config (TP8 - * C=4 in the GLM5.2 B300 hicache run) look like a pareto point: its X marker - * stayed visible sitting on the dashed roofline (the monotone spline between - * C=8 and C=2 passes within ~0.5% of it) while the official twin was hidden. - * - * Fixture values are the real run-29682242847 numbers: - * conc, p90_intvty, tput_per_gpu, p90_e2el - * C=4 is dominated on e2e by C=8 (12874 tok/s @ 33.1s vs 9415 @ 48.0s), so - * with Optimal Only ON exactly 4 of the 5 overlay X's must stay visible. + * Regression: with Optimal Only ON (the default — `i_optimal !== '0'`), + * official non-pareto points are hidden via `isPointVisible`, but overlay X + * markers rendered every point unconditionally. On the agentic interactivity + * chart this made an e2e-dominated config (TP8 C=4 in the GLM5.2 B300 hicache + * run) look like a pareto point: its X marker stayed visible sitting on the + * dashed roofline (the monotone spline between C=8 and C=2 passes within + * ~0.5% of it) while the official twin was hidden. */ import { unlockAgenticGate } from '../support/e2e'; - -const DEFAULT_MODEL_DB_KEY = 'dsv4'; -const AGENTIC_DATE = '2026-07-19'; -const OVERLAY_RUN_ID = '29682242847'; -const OVERLAY_RUN_URL = `https://github.com/SemiAnalysisAI/InferenceX/actions/runs/${OVERLAY_RUN_ID}`; - -const REAL_CONFIGS: [number, number, number, number][] = [ - [48, 10.6, 17199, 126.9], - [8, 68.5, 12874, 33.1], - [4, 88.3, 9415, 48], // e2e-dominated by C=8 → NOT optimal - [2, 111.1, 5018, 30], - [1, 130.2, 2600, 25.8], -]; - -const metricsFor = (intvty: number, tput: number, e2el: number): Record => ({ - median_itl: 1 / (intvty * 1.2), - p90_itl: 1 / intvty, - p99_itl: 1 / (intvty * 0.8), - median_e2el: e2el * 0.8, - p90_e2el: e2el, - p99_e2el: e2el * 1.3, - median_ttft: 0.5, - p90_ttft: 1, - p99_ttft: 2, - tput_per_gpu: tput, - output_tput_per_gpu: tput * 0.3, - input_tput_per_gpu: tput * 0.7, -}); - -let idCursor = 900000; -const b300Rows = (runUrl: string | null) => - REAL_CONFIGS.map(([conc, intvty, tput, e2el]) => ({ - id: runUrl ? 0 : idCursor++, - hardware: 'b300', - framework: 'sglang', - model: DEFAULT_MODEL_DB_KEY, - precision: 'fp4', - spec_method: 'none', - disagg: false, - is_multinode: false, - prefill_tp: 8, - decode_tp: 8, - num_prefill_gpu: 8, - num_decode_gpu: 8, - isl: null, - osl: null, - conc, - offload_mode: 'on', - benchmark_type: 'agentic_traces', - image: 'sglang:test', - metrics: metricsFor(intvty, tput, e2el), - workers: null, - date: AGENTIC_DATE, - run_url: runUrl, - })); - -const availability = [ - { - model: DEFAULT_MODEL_DB_KEY, - isl: null, - osl: null, - precision: 'fp4', - hardware: 'b300', - framework: 'sglang', - spec_method: 'none', - disagg: false, - benchmark_type: 'agentic_traces', - date: AGENTIC_DATE, - }, -]; - -const countVisible = ($els: JQuery): number => - [...$els].filter((el) => getComputedStyle(el).opacity !== '0').length; +import { + countVisible, + interceptOverlayRun, + OVERLAY_RUN_ID, + REAL_CONFIGS, +} from '../support/overlay-fixtures'; describe('Overlay points respect Optimal Only (agentic interactivity)', () => { before(() => { - cy.intercept('GET', '/api/v1/availability', { body: availability }).as('availability'); - cy.intercept('GET', '/api/v1/benchmarks*', { body: b300Rows(null) }).as('benchmarks'); - cy.intercept('GET', '/api/unofficial-run*', { - body: { - runInfos: [ - { - id: OVERLAY_RUN_ID, - name: 'add-glm5.2-b300-agentic-hicache', - branch: 'add-glm5.2-b300-agentic-hicache', - sha: 'abc000', - createdAt: `${AGENTIC_DATE}T00:00:00Z`, - url: OVERLAY_RUN_URL, - conclusion: 'success', - status: 'completed', - isNonMainBranch: true, - }, - ], - benchmarks: b300Rows(OVERLAY_RUN_URL), - evaluations: [], - }, - }).as('unofficialRun'); + interceptOverlayRun(); cy.visit(`/inference?unofficialrun=${OVERLAY_RUN_ID}&i_seq=agentic-traces&i_pctl=p90`, { onBeforeLoad(win) { win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now())); @@ -136,11 +45,11 @@ describe('Overlay points respect Optimal Only (agentic interactivity)', () => { it('hides the e2e-dominated overlay point in the default Optimal Only view', () => { cy.get('#scatter-hide-non-optimal').should('have.attr', 'data-state', 'checked'); // Official parity check: 4 of 5 official dots visible. - cy.get('[data-testid="inference-chart-display"] svg .dot-group').then(($dots) => { + cy.get('[data-testid="inference-chart-display"] svg .dot-group').should(($dots) => { expect(countVisible($dots), 'visible official points').to.eq(REAL_CONFIGS.length - 1); }); // The overlay must hide its C=4 too — 4 of 5 X markers visible. - cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').then(($pts) => { + cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').should(($pts) => { expect(countVisible($pts), 'visible overlay X markers').to.eq(REAL_CONFIGS.length - 1); }); }); @@ -148,7 +57,7 @@ describe('Overlay points respect Optimal Only (agentic interactivity)', () => { it('shows all overlay points when Optimal Only is turned off', () => { cy.get('#scatter-hide-non-optimal').click(); cy.get('#scatter-hide-non-optimal').should('have.attr', 'data-state', 'unchecked'); - cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').then(($pts) => { + cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').should(($pts) => { expect(countVisible($pts), 'visible overlay X markers').to.eq(REAL_CONFIGS.length); }); }); @@ -156,7 +65,7 @@ describe('Overlay points respect Optimal Only (agentic interactivity)', () => { it('re-hides the e2e-dominated overlay point when Optimal Only is re-enabled', () => { cy.get('#scatter-hide-non-optimal').click(); cy.get('#scatter-hide-non-optimal').should('have.attr', 'data-state', 'checked'); - cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').then(($pts) => { + cy.get('[data-testid="inference-chart-display"] svg .unofficial-overlay-pt').should(($pts) => { expect(countVisible($pts), 'visible overlay X markers').to.eq(REAL_CONFIGS.length - 1); }); }); diff --git a/packages/app/cypress/support/overlay-fixtures.ts b/packages/app/cypress/support/overlay-fixtures.ts new file mode 100644 index 00000000..ccac5533 --- /dev/null +++ b/packages/app/cypress/support/overlay-fixtures.ts @@ -0,0 +1,108 @@ +/** + * Shared fixtures for `?unofficialrun=` overlay e2e specs. + * + * The benchmark values are the real numbers from GitHub run 29682242847 + * (GLM5.2 B300 agentic hicache, offload=on rows): + * conc, p90_intvty (tok/s/user), tput_per_gpu, p90_e2el (s) + * C=4 is dominated on e2e by C=8 (12874 tok/s @ 33.1s vs 9415 @ 48.0s), which + * makes the set a ready-made probe for the e2e-restricted frontier behaviors. + */ +export const DEFAULT_MODEL_DB_KEY = 'dsv4'; +export const AGENTIC_DATE = '2026-07-19'; +export const OVERLAY_RUN_ID = '29682242847'; +export const OVERLAY_RUN_BRANCH = 'add-glm5.2-b300-agentic-hicache'; +export const OVERLAY_RUN_URL = `https://github.com/SemiAnalysisAI/InferenceX/actions/runs/${OVERLAY_RUN_ID}`; + +export const REAL_CONFIGS: [number, number, number, number][] = [ + [48, 10.6, 17199, 126.9], + [8, 68.5, 12874, 33.1], + [4, 88.3, 9415, 48], // e2e-dominated by C=8 → NOT optimal + [2, 111.1, 5018, 30], + [1, 130.2, 2600, 25.8], +]; + +export const metricsFor = (intvty: number, tput: number, e2el: number): Record => ({ + // intvty is ALWAYS derived as 1/itl by the agentic aliases — feed itl. + median_itl: 1 / (intvty * 1.2), + p90_itl: 1 / intvty, + p99_itl: 1 / (intvty * 0.8), + median_e2el: e2el * 0.8, + p90_e2el: e2el, + p99_e2el: e2el * 1.3, + median_ttft: 0.5, + p90_ttft: 1, + p99_ttft: 2, + tput_per_gpu: tput, + output_tput_per_gpu: tput * 0.3, + input_tput_per_gpu: tput * 0.7, +}); + +let idCursor = 900000; +export const b300Rows = (runUrl: string | null) => + REAL_CONFIGS.map(([conc, intvty, tput, e2el]) => ({ + id: runUrl ? 0 : idCursor++, + hardware: 'b300', + framework: 'sglang', + model: DEFAULT_MODEL_DB_KEY, + precision: 'fp4', + spec_method: 'none', + disagg: false, + is_multinode: false, + prefill_tp: 8, + decode_tp: 8, + num_prefill_gpu: 8, + num_decode_gpu: 8, + isl: null, + osl: null, + conc, + offload_mode: 'on', + benchmark_type: 'agentic_traces', + image: 'sglang:test', + metrics: metricsFor(intvty, tput, e2el), + workers: null, + date: AGENTIC_DATE, + run_url: runUrl, + })); + +export const availability = [ + { + model: DEFAULT_MODEL_DB_KEY, + isl: null, + osl: null, + precision: 'fp4', + hardware: 'b300', + framework: 'sglang', + spec_method: 'none', + disagg: false, + benchmark_type: 'agentic_traces', + date: AGENTIC_DATE, + }, +]; + +/** Intercept availability + benchmarks + unofficial-run with the B300 fixture. */ +export const interceptOverlayRun = () => { + cy.intercept('GET', '/api/v1/availability', { body: availability }).as('availability'); + cy.intercept('GET', '/api/v1/benchmarks*', { body: b300Rows(null) }).as('benchmarks'); + cy.intercept('GET', '/api/unofficial-run*', { + body: { + runInfos: [ + { + id: OVERLAY_RUN_ID, + name: OVERLAY_RUN_BRANCH, + branch: OVERLAY_RUN_BRANCH, + sha: 'abc000', + createdAt: `${AGENTIC_DATE}T00:00:00Z`, + url: OVERLAY_RUN_URL, + conclusion: 'success', + status: 'completed', + isNonMainBranch: true, + }, + ], + benchmarks: b300Rows(OVERLAY_RUN_URL), + evaluations: [], + }, + }).as('unofficialRun'); +}; + +export const countVisible = ($els: JQuery): number => + [...$els].filter((el) => getComputedStyle(el).opacity !== '0').length; diff --git a/packages/app/src/components/inference/ui/ScatterGraph.tsx b/packages/app/src/components/inference/ui/ScatterGraph.tsx index 551a1b2a..2d7b5492 100644 --- a/packages/app/src/components/inference/ui/ScatterGraph.tsx +++ b/packages/app/src/components/inference/ui/ScatterGraph.tsx @@ -587,6 +587,26 @@ const ScatterGraph = React.memo( [overlayData, unifiedToggle, toggleHwType], ); + // Legend "X" (remove) — same overlay split as handleToggleHwType. With an + // overlay loaded the chart reads localOfficialOverride, which the context's + // removeHwType (activeHwTypes) never touches, so routing the X through it + // left the official series visibly un-removed. Commit the removal through + // the unified selection instead; context state stays untouched so + // dismissing the overlay restores the pre-overlay official selection, same + // as the toggle path. + const handleRemoveHwType = useCallback( + (key: string) => { + if (!overlayData) { + removeHwType(key); + return; + } + const next = new Set(resolvedUnifiedSelection); + next.delete(key); + commitUnifiedSelection(next); + }, + [overlayData, removeHwType, resolvedUnifiedSelection, commitUnifiedSelection], + ); + // --- Theme --- const hardwareConfig = hardwareConfigOverride || contextHardwareConfig; const activeHwKeys = useMemo(() => { @@ -2820,7 +2840,7 @@ const ScatterGraph = React.memo( variant="sidebar" onItemHover={handleLegendHover} onItemHoverEnd={handleLegendHoverEnd} - onItemRemove={showAllHardwareTypes ? undefined : removeHwType} + onItemRemove={showAllHardwareTypes ? undefined : handleRemoveHwType} legendItems={[ // Overlay legend: one entry per loaded unofficial run that actually // contributes points to this chart. Colored from the shared palette From 6fcc13fe699badee7614d1f49b965ea7e8025e8b Mon Sep 17 00:00:00 2001 From: Oseltamivir <58582368+Oseltamivir@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:38:36 +0800 Subject: [PATCH 2/2] feat(legend): explicit hide/show affordances on legend rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the two legend actions visually explicit: hovering an ACTIVE row swaps the color dot for the "Hide