Skip to content

fix(inference): ungate Measured Energy axes and give them a Pareto direction / 开放实测能耗坐标轴并补齐 Pareto 方向 - #720

Merged
edwingao28 merged 6 commits into
masterfrom
fix/ungate-measured-energy-axes
Aug 11, 2026
Merged

fix(inference): ungate Measured Energy axes and give them a Pareto direction / 开放实测能耗坐标轴并补齐 Pareto 方向#720
edwingao28 merged 6 commits into
masterfrom
fix/ungate-measured-energy-axes

Conversation

@edwingao28

@edwingao28 edwingao28 commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

The Measured Energy group was hidden behind the konami feature gate. But the gate only ever hid the dropdown row — InferenceContext reads i_metric straight from the URL with no allowlist, so ?i_metric=y_measuredJPerOutputToken already renders these charts in production today, on official data, for anyone who types the URL. This makes the group selectable from the dropdown, and fixes the two bugs that URL path already exposes.

Three of the six axes had no _roofline direction: y_measuredAvgPower, y_measuredPrefillAvgPower, y_measuredDecodeAvgPower (the three J/token axes already had one). Without a direction:

  • InferenceTable sorts a lower-is-better metric descending — the highest-wattage config lands at the top of the list.
  • ScatterGraph falls back to lower_right, which is correct on the interactivity block but backwards on the e2e block, where every lower-is-better metric is lower_left. The published "optimal" line traces the slowest config at each power level.

Both are reachable today via ?i_metric=.

The gate mechanism itself is kept — useFeatureGate still drives the hidden tab popover in tab-nav.tsx.

Knowingly out of scope, each pre-existing and reachable today:

  • Selecting a partially-covered measured axis prunes configs from the legend and does not restore them on switching back (useChartContext.ts reconcileActiveSet). Needs integration coverage; separate PR.
  • Empty-state copy does not explain that most configs have no power data yet (ScatterGraph.tsx, GPUGraph.tsx).
  • Historical Trends mounts the same ChartControls, so Measured Energy appears there too, keeping its own generic empty-state message.
  • joules_per_output_token has two vintages on disaggregated rows: per-stage for rows produced before 2026-08-05, cluster-wide for rows from aggregate_power_multinode.py (merged in InferenceX on 2026-08-05). Nothing on a row distinguishes them. Tracked separately; it does not bite today because multi-node runs are not yet instrumented in production.

Verified: bun run typecheck, bun run lint, bun run fmt clean; bun run test:unit 3730 tests pass across all four workspaces. Cypress not run locally (needs a server + DB); the only spec touched is measured-power-overlay.cy.ts.

中文说明

实测能耗(Measured Energy)分组此前被 konami 特性开关隐藏。但该开关只隐藏了下拉菜单中的选项行——InferenceContext 直接从 URL 读取 i_metric 且不做白名单校验,因此任何人手动输入 ?i_metric=y_measuredJPerOutputToken,今天就能在生产环境看到这些图表,用的是正式数据。本次改动让该分组可以正常从下拉菜单选择,并修复该 URL 路径已经暴露出来的两个缺陷。

六个坐标轴中有三个缺少 _roofline 方向声明:y_measuredAvgPowery_measuredPrefillAvgPowery_measuredDecodeAvgPower(三个 J/token 坐标轴已经有了)。缺少方向声明会导致:

  • InferenceTable 对“越低越好”的指标按降序排列,功耗最高的配置排在列表最前。
  • ScatterGraph 回退到 lower_right。这在 interactivity 图表块上恰好正确,但在 e2e 图表块上是反的——该块中所有“越低越好”的指标都是 lower_left,于是发布出去的“最优”曲线画的是每个功耗水平下最慢的配置。

这两个问题今天通过 ?i_metric= 就能触发。

特性开关机制本身予以保留——useFeatureGate 仍在 tab-nav.tsx 中驱动隐藏标签页的弹层。

以下为明确不在本次范围内的已知问题,均为既有问题且今天即可触发:

  • 选择覆盖不完整的实测坐标轴会从图例中剔除部分配置,切回时不会恢复(useChartContext.tsreconcileActiveSet)。需要集成测试覆盖,另开 PR。
  • 空状态文案没有说明多数配置尚无功耗数据(ScatterGraph.tsxGPUGraph.tsx)。
  • Historical Trends 挂载了同一个 ChartControls,因此实测能耗也会出现在该标签页,其空状态沿用原有通用文案。
  • 分离式(disaggregated)行的 joules_per_output_token 存在两种口径:2026-08-05 之前产生的行为分阶段口径,之后由 aggregate_power_multinode.py 产生的行为全集群口径(该聚合器于 2026-08-05 合入 InferenceX),而行上没有任何字段可以区分。已单独跟踪;由于生产环境的多节点运行尚未接入功耗采集,目前不会实际影响图表。

验证:bun run typecheckbun run lintbun run fmt 均通过;bun run test:unit 四个 workspace 共 3730 项测试全部通过。Cypress 未在本地运行(需要服务端与数据库);本次改动的 spec 仅 measured-power-overlay.cy.ts 一个。


Note

Medium Risk
Touches core inference chart selection, URL sharing, and legend reconciliation; behavior change is intentional but affects many code paths in InferenceContext.

Overview
Measured Energy is no longer hidden behind the konami feature gate in the Y-axis dropdown, and Cypress no longer sets inferencex-feature-gate for overlay tests.

The three watt-based measured metrics get _roofline entries in chart config (lower_right on interactivity, lower_left on e2e) so table sort, Pareto frontiers, and Optimal Only use lower-is-better behavior instead of the previous lower_right fallback. Table sorting moves into shared sortRowsByYMetric, with regression tests on frontier corners and row order.

Legend hardware selection is decoupled from the active Y-metric: useChartData exposes selectionPoints via applyScopeFilters (GPU/quick-filter/compare only), and InferenceContext drives toggles, presets, URL i_active, and resets from selectableHwTypes while hwTypesWithData stays metric-aware for what the chart draws. That stops Measured Energy axes from permanently pruning configs without telemetry when users switch metrics. Historical Trends’ “reset filter” action now treats a partial legend selection as filtered even when sizes match.

Reviewed by Cursor Bugbot for commit f021de6. Bugbot is set up for automated code reviews on this repo. Configure here.

…rection

The Measured Energy group was hidden behind the konami feature gate, but the
gate only ever hid the dropdown row: InferenceContext reads i_metric straight
from the URL with no allowlist, so ?i_metric=y_measuredJPerOutputToken already
renders these charts in production today, on official data. Make the group
selectable.

Three of the six axes (y_measuredAvgPower, y_measuredPrefillAvgPower,
y_measuredDecodeAvgPower) had no _roofline direction. Without one,
InferenceTable sorts a lower-is-better metric descending, and ScatterGraph
falls back to lower_right, which is backwards on the e2e block. Declare the
direction in both chart blocks, matching the J/token axes.

中文:实测能耗坐标轴组此前被 konami 特性开关隐藏,但该开关只隐藏了下拉选项本身——
InferenceContext 直接从 URL 读取 i_metric 且不做白名单校验,因此
?i_metric=y_measuredJPerOutputToken 今天就能在生产环境渲染这些图表,用的是正式数据。
本次改动让该分组可以正常选择。六个坐标轴中有三个缺少 _roofline 方向声明,导致表格视图
对“越低越好”的指标按降序排列,且 ScatterGraph 回退到 lower_right——在 e2e 图表块上方向
是反的。现为这三个坐标轴在两个图表块中补齐方向声明,与 J/token 系列保持一致。
@edwingao28
edwingao28 requested a review from adibarra as a code owner August 11, 2026 00:24
@edwingao28
edwingao28 requested a review from adibarra August 11, 2026 00:24
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
inferencemax-app Ready Ready Preview Aug 11, 2026 4:51pm

Request Review

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Claude finished @edwingao28's task in 3m 1s —— View job


🟢 LGTM — no blocking issues found

Reviewed the diff and verified the changed behavior at runtime against the real read-only DB.

What the PR does (all sound):

  • Ungates the Measured Energy metric group by dropping gated: true in ChartControls.tsx — the group is now selectable from the Y-Axis Metric dropdown.
  • Adds _roofline directions to the three power axes (y_measuredAvgPower, y_measuredPrefillAvgPower, y_measuredDecodeAvgPower) in both config blocks. The values are consistent with every sibling lower-is-better metric: lower_right on the interactivity block, lower_left on the e2e block (matches cost* and j* axes).
  • Test rename in utils.test.ts is a necessary correction — the old test asserted "no roofline direction" against y_measuredAvgPower, which now has a direction; it's re-pointed at a genuinely-undefined metric key (y_noRooflineDirection) so it still exercises the undefined-direction overlay path.

Runtime verification (localhost, real data):

  • Measured Average Power per Chip vs. Interactivity renders real points; optimal frontier hugs bottom-right (low power / high interactivity) — correct for lower_right. [screenshot]
  • Measured Average Power per Chip vs. End-to-end Latency renders; frontier hugs bottom-left — correct for lower_left. [screenshot]
  • ✅ Metric selectable from the Y-Axis dropdown without the konami gate.
  • ✅ 0 console errors on both blocks.

CI: typecheck/lint/component + E2E shards (incl. the touched measured-power-overlay.cy.ts path), Cursor Bugbot all passing; a few shards still pending at review time.

Minor, non-blocking: after this change no metric group in ChartControls is gated: true, so useFeatureGate + the gated filter there are now effectively no-ops within that file. That's intentional per the PR (the gate mechanism is retained for tab-nav.tsx), so no action needed — just noting it in case a future reader wonders.

The four out-of-scope items called out in the PR description (partial-coverage legend pruning, empty-state copy, Historical Trends reuse, disagg J/token vintage) are pre-existing and reachable today via ?i_metric=; deferring them is reasonable.
fix/ungate-measured-energy-axes

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b129bb8f50

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/app/src/components/inference/ui/ChartControls.tsx
Comment thread packages/app/src/components/inference/utils.test.ts
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Claude finished @edwingao28's task in 2m 51s —— View job


Re-review: 🟢 LGTM with one non-blocking test-coverage note

The fix itself is correct and I confirmed it at runtime. Since the prior review (LGTM), the only new commit is a master merge — the substantive diff is unchanged, so I focused on the two open Codex P1 threads.

  • Reviewed diff (config directions, gate removal, test rename)
  • Evaluated open Codex P1 comments
  • Checked CI status
  • Runtime verification
  • Posted findings

Correctness — verified. Dumped every _roofline in inference-chart-config.json: the three new power directions match every sibling lower-is-better metric (cost*, j*) exactly — lower_right on the interactivity block, lower_left on e2e. Runtime (localhost, real read-only DB): Measured Average Power per Chip is selectable with no konami gate, renders real points, Optimal Only on, 0 console errors.

CI: typecheck/unit, lint, component, Chrome E2E shards (incl. the touched measured-power-overlay.cy.ts path) all green; some Firefox shards + Bugbot still pending at review time.

🟡 Non-blocking — regression coverage gap (agrees with Codex P1 #2): the fix lives entirely in JSON config values, but no touched test asserts them — reversing lower_right/lower_left would still pass. I left an inline suggestion for a ~5-line test that reads the real config and asserts the three power axes sort ascending on both blocks. Cheap insurance against a one-character reversion; not blocking since correctness is verified today.

On Codex P1 #1 (reconcileActiveSet prunes configs on metric switch): real, but pre-existing and reachable today via ?i_metric=, explicitly scoped out in the PR description. Deferring to a separate PR with integration coverage is reasonable — not introduced here, so not blocking this PR.

No blocking issues. Good to merge; adding the config-direction regression test before merge would be the ideal close-out.
fix/ungate-measured-energy-axes

@edwingao28
edwingao28 enabled auto-merge (squash) August 11, 2026 04:12
Comment thread packages/app/src/components/inference/utils.test.ts
…ches

Ungating the Measured Energy axes made a destructive flow part of the normal
UI. Those axes only exist for configs whose run recorded power telemetry;
useChartData drops the rest, and the legend's active set was reconciled against
that metric-filtered set. reconcileActiveSet never re-widens, so switching back
to throughput left the pruned configs deselected until the user reset them.

Split the two sets instead of tracking and restoring: activeHwTypes now lives
in a metric-independent universe (selectableHwTypes, built from the scope
filters only), while hwTypesWithData stays metric-aware and drives what the
legend renders and the chart draws. Nothing is pruned, so nothing has to be
restored, and a config the user removed by hand stays off. Toggle, select-all,
preset, URL-restore, reset, and i_active serialization all move to the
universe so their set-size comparisons stay consistent.

Also add the regression coverage the new _roofline values were missing: the
measured-power frontier picks the lower-right corner on the interactivity block
and the lower-left corner on e2e, dominated points stay out of the Optimal Only
set, and the table sorts ascending. The table sort moved to a pure helper so it
can be asserted directly. Flipping or deleting the three _roofline values makes
these tests fail.

中文:解除 Measured Energy 坐标轴的隐藏,等于把一条破坏性流程带进了常规交互。这些坐标轴
只对记录了功耗遥测的配置存在,useChartData 会过滤掉其余配置,而图例的选中集合此前正是与
这个按指标过滤后的集合做交集。reconcileActiveSet 只收不放,因此切回吞吐量坐标轴时,被裁掉
的配置仍处于未选中状态,只能靠重置或刷新恢复。

本次改为拆分两个集合,而不是记录后再补回:activeHwTypes 现在处于与指标无关的全集
(selectableHwTypes,仅由作用域过滤器构成),hwTypesWithData 仍与指标相关,负责决定图例
渲染和图表绘制的内容。既然不再裁剪,就无需恢复;用户手动移除的配置也会保持关闭。图例切换、
全选、预设、URL 还原、重置以及 i_active 序列化都改用该全集,以保证各处的集合规模比较一致。

同时补上新增 _roofline 值缺失的回归测试:实测功耗的 Pareto 前沿在 interactivity 图表块取
右下角、在 e2e 图表块取左下角,被支配的点不会进入 Optimal Only 集合,表格按升序排列。表格
排序逻辑抽成纯函数以便直接断言。翻转或删除这三个 _roofline 值都会让这些测试失败。
The selection universe is no longer metric-filtered, so activeHwTypes and
hwTypesWithData can now come from different sets and a size comparison is not a
valid "something is deselected" test — on a partially-covered axis the active
set is the larger one, which would hide the reset action exactly when it is
needed.

中文:选择范围不再按指标过滤,activeHwTypes 与 hwTypesWithData 可能来自不同集合,
因此用集合大小比较判断"是否有取消勾选"已不成立——在覆盖不完整的坐标轴上,活动集合反而更大,
会在最需要重置操作时把它隐藏掉。改为按成员判断。
Resolves the InferenceContext conflict between the metric-independent
selection universe added here and the bestPerSku / bestHwTypes mode that
landed on master meanwhile. bestPerSku keeps deriving from the current axis
by design; every automatic full-set write falls back to selectableHwTypes
instead of the metric-filtered set.

中文:解决 InferenceContext 的冲突——本分支引入的"与指标无关的可选范围"与期间合入 master 的
bestPerSku / bestHwTypes 模式相遇。bestPerSku 按设计仍随当前坐标轴推导;其余所有自动写入完整
集合的位置改用 selectableHwTypes,而不是按指标过滤后的集合。
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Claude finished @edwingao28's task in 7m 52s —— View job


Re-review: 🔴 One blocking issue — new E2E spec fails in CI

Focused on the new commits since the last LGTM (ba40589, 2a53b96, merge b4d3e17), which address the two open Codex P1 threads.

🔴 BLOCKING — the new spec measured-metric-legend-persistence.cy.ts fails deterministically on Chrome and Firefox shard 2 (all 3 retries): Expected to find element: [title^="Show H200"], but never found it. Inline detail →

Root cause: the legend-reset effect (InferenceContext.tsx:1091) is keyed on hwResetKey, which includes selectedYAxisMetric (InferenceContext.tsx:1039-1041, pre-existing on master). So every Y-axis metric switch re-fires the reset and runs setActiveHwTypes(bestPerSku ? bestHwTypes : selectableHwTypes), re-selecting all configs. The round trip through Measured Power therefore resurrects the hand-removed H200 — it comes back active, so the title="Show H200" restore affordance never renders. The first test passes only because the same blunt reset re-adds the pruned config; both contracts can't hold while the reset fires on metric switches. Suggested fix: drop selectedYAxisMetric from hwResetKey so a metric-only switch doesn't reset the legend (persisted activeHwTypes + render-time intersection with hwTypesWithData then satisfy both tests); keep selectedXAxisMode for the best-per-sku recompute.

The rest of the follow-up is sound:

  • P1 chore: bump anthropics/claude-code-action from 04f907ba2a4c77706a56b4078c682d250716cc0c to cd77b50d2b0808657f8e6774085c8bf54484351c #2 (regression coverage) — resolved. measured-power-direction.test.ts runs the real inference-chart-config.json through the production frontier (paretoFrontForDirection) and sort (sortRowsByYMetric) helpers, asserting [1,32] (lower-right/interactivity), [256,32] (lower-left/e2e), dominated-config exclusion, and ascending table order. A one-character direction reversion would now fail. Extracting sortRowsByYMetric into inference-table-sort.ts is a clean, testable seam.
  • P1 chore: bump the all-minor-patch group with 2 updates #1 (legend pruning) — architecture is correct. Splitting the metric-independent selection universe (selectableHwTypesselectionPoints/applyScopeFilters) from the metric-aware render set (hwTypesWithData) is the right model; the legend/reset/URL consumers were consistently repointed, and ScatterGraph's reset action stays correct because effectiveOfficialHwTypes is always intersected with hwTypesWithData. The HistoricalTrendsDisplay change from a size comparison to membership (some(key => !activeHwTypes.has(key))) correctly compensates for the now-wider activeHwTypes. The only gap is the reset-on-metric-switch above, which undercuts the second half of the contract the fix set out to guarantee.

CI: Lint & Format, Tests (Unit), Code Quality all green; Tests (E2E) failing (shard 2, both browsers) — the blocking item above.

Once the reset-key change lands and shard 2 goes green, this is good to merge.
fix/ungate-measured-energy-axes

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b4d3e17. Configure here.

Comment thread packages/app/src/components/inference/InferenceContext.tsx
@blacksmith-sh

This comment has been minimized.

Comment thread packages/app/cypress/e2e/measured-metric-legend-persistence.cy.ts Outdated
It guarded nothing: the passing half also passes with the InferenceContext fix reverted, and the failing half asserted that a hand-removed config survives a Y-metric switch, which hwResetKey has always reset by design. useChartData.test.ts covers the fix.
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Claude finished @edwingao28's task in 3m 34s —— View job


Re-review: 🟢 LGTM — prior blocking issue resolved

  • Diffed the new commits since the last review
  • Assessed the resolution of the prior 🔴 BLOCKING E2E failure
  • Checked CI status (all green)
  • Runtime verification of the Measured Energy axes
  • Posted findings

The one new commit since the last review is f021de6 — "drop the legend-persistence e2e spec". That deletes measured-metric-legend-persistence.cy.ts, the spec whose shard-2 failure I flagged as blocking last time. Dropping it is the right call, and here's why it isn't just muting a red test:

  • Passing half was tautological. restores the telemetry-less config after a round trip passes even with the InferenceContext reconcile fix reverted — the hwResetKey reset effect (keyed on selectedYAxisMetric) re-fires on the switch back to throughput and re-adds every config, so the assertion never actually exercised the metric-independent reconcile.
  • Failing half asserted a contract the code never had. keeps a hand-removed config off across the same round trip required a hand-deselection to survive a Y-axis switch, but that same hwResetKey reset has always re-widened the legend on a metric switch (pre-existing on master). The two halves couldn't both hold while the reset fires on metric switches — so the spec was internally contradictory, not a real regression guard.

Regression coverage for the fix that actually shipped is retained at the unit level, and it targets the real mechanism rather than the incidental reset:

  • useChartData.test.tsapplyScopeFilters keeps telemetry-less configs (the selection universe is metric-independent).
  • useChartContext.test.tsreconcileActiveSet never re-widens, documenting exactly why it must not be handed a metric-filtered set.
  • measured-power-direction.test.ts runs the real config through the production frontier/sort helpers (already validated in the prior review).

So the deleted spec guarded nothing the remaining tests don't, and its removal turns shard 2 green.

CI: Lint & Format, Tests (Unit), Tests (E2E), Code Quality — all green.

Runtime (localhost, real read-only DB): ?i_metric=y_measuredAvgPower selects Measured Average Power per Chip from the Y-Axis dropdown with no konami gate; the interactivity block renders real points (330–440 W), legend is metric-aware (only telemetry-carrying configs), Optimal Only + Best per SKU on, 0 console errors.

The four out-of-scope items in the PR description remain pre-existing and reachable today via ?i_metric=; deferring them is reasonable. No blocking issues — good to merge.
fix/ungate-measured-energy-axes

@edwingao28
edwingao28 merged commit 1fd5e8d into master Aug 11, 2026
24 checks passed
@edwingao28
edwingao28 deleted the fix/ungate-measured-energy-axes branch August 11, 2026 17:12
chunfangamd added a commit to chunfangamd/InferenceX-app that referenced this pull request Aug 11, 2026
Pulls in SemiAnalysisAI#720 (Measured Energy axes). No conflicts: that change is confined
to the inference chart components and shares no files with the overview work
on this branch. Full unit suite, typecheck, lint and format are clean on the
merged tree.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant