Skip to content

feat(app): role-local measured energy axes + per-worker power drilldown | 前端:新增角色级实测能耗坐标轴与每 worker 功耗下钻 - #935

Open
edwingao28 wants to merge 4 commits into
feat/measured-power-tieringfrom
feat/worker-power-drilldown
Open

feat(app): role-local measured energy axes + per-worker power drilldown | 前端:新增角色级实测能耗坐标轴与每 worker 功耗下钻#935
edwingao28 wants to merge 4 commits into
feat/measured-power-tieringfrom
feat/worker-power-drilldown

Conversation

@edwingao28

@edwingao28 edwingao28 commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Important

STACKED PR — base branch is feat/measured-power-tiering (#934). That PR must merge first; this diff contains only this PR's deltas on top of it. Do not squash-merge this PR into master before #934 lands.

What

Three connected issues left measured-power data invisible or untested in the app frontend:

  1. workers[] was plumbed end-to-end but nothing rendered it. The type, transform (benchmark-transform.ts), and API docs all carry per-worker power, yet no tooltip/dialog/page consumed it.
  2. Role-local energy had no y-axis. prefill_joules_per_input_token / decode_joules_per_output_token are populated whenever power_valid !== 0, but users could only plot whole-deployment J/tok and role watts — not the role energy efficiency that separates prefill-bound from decode-bound disagg deployments.
  3. No frontend test covered AgentX + measured power. All transform power tests used single_turn; both power cypress specs were fixed-seq only.

Changes

Two new registry axes (Measured Energy group)

  • y_measuredPrefillJPerInputTokenMeasured Prefill Joules per Input Token / 每输入 token 实测 Prefill 能耗
  • y_measuredDecodeJPerOutputTokenMeasured Decode Joules per Output Token / 每输出 token 实测 Decode 能耗
  • Both polarity: 'lower' (Pareto lower_right on interactivity / lower_left on e2e derives automatically), bilingual labels/titles, entries in METRIC_EXPLANATIONS with the certification-tier note, and membership in MEASURED_ENERGY_METRIC_CONFIG_KEYS — so the tier ring / tooltip tier line / footer key from feat(inference): distinguish certified vs legacy measured power with quick filter | 推理图表:区分已认证与旧版实测功耗并新增快捷筛选 #934 apply to them too.
  • buildMeasuredPowerChartFields maps the two role scalars into {y, roof} chart fields; tokenMetricTypeForConfigKey classifies them input/output by substring (asserted in tests).
  • Coverage filtering is generic: series without role energy (single-node aggregated, legacy rows) drop off the chart and out of the rendered legend when the axis is selected — the rendered legend is metric-aware (hwTypesWithData). The switch is non-destructive: the selection universe (selectableHwTypes) never intersects metric coverage, so switching back to an axis both series carry restores the series with no legend re-selection (asserted in the e2e spec).

WorkerPower drilldown in the pinned chart tooltip

  • New generateWorkerPowerHTML(d, isPinned, locale) in tooltipUtils.ts, rendered by all three generators (official, overlay, GPU-compare) only when pinned and workers is non-empty.
  • Per row: role[worker_idx] · N chips · W, plus temp (avg/peak°C), util %, mem GiB, and hosts when present. Capped at 8 rows with a bilingual +N more workers line. role and hosts are HTML-escaped (they arrive from artifact JSONB).
  • avg_mem_used_mb is read as MiB (nvidia-smi convention) → /1024 GiB; commented in code, to be confirmed against the producer in the AgentX power artifacts change (feat/agentx-power-artifacts in InferenceX, PR queued).

Tests

  • Transform unit tests: benchmark_type='agentic_traces' with the full measured-power payload (pass-through incl. workers) and the power_valid: 0 scrub mirror.
  • chart-utils.test.ts: emission / absence / zero-preservation for the two new chart fields.
  • measured-power-direction.test.ts: Pareto corners + bilingual labels for both new config keys.
  • metric-registry.test.ts: token-type classification + share-link round-trip; the pre-existing registry⇄groups parity test enforces group membership.
  • tooltip-utils.test.ts: worker table across all three generators — pinned/unpinned, empty/absent, 8-row cap, ZH strings, HTML escaping.
  • New e2e spec cypress/e2e/agentic-measured-power.cy.ts (spec-scoped intercepts, synthetic fixtures via new measuredPowerMetrics() / syntheticWorkers() helpers in agentic-fixtures.ts): both axes visible under Measured Energy, aggregate series coverage-filtered off chart and legend with a non-destructive switch back, worker table on a pinned agentic point, graceful absence on a workers-less point. Registered in timings.json for cypress-split.

Data caveat

Production workers[] is currently empty for AgentX runs — producer-side emission lands with feat/agentx-power-artifacts (InferenceX). The UI renders gracefully with workers absent/empty (verified by unit tests and the e2e spec's aggregate series); the synthetic cypress fixtures verify the drilldown ahead of real data. Fixed-seq multinode disagg points already carry real workers today.

Backward compatibility

  • Legacy rows (no workers, no role energy, no power_valid) render exactly as before — the untouched pre-existing transform/chart/tooltip tests stay green.
  • New share-link metric keys degrade safely on old deploys via resolveMetricConfigKey (fall back to the default axis); old links are unaffected.
  • No schema, API, or stored-state migration; a single revert restores prior behavior.

Review notes

Findings from review of the initial push, with resolutions:

  1. Fixed in 703aaae0: the e2e spec's first test asserted the legend still contains B300 after selecting the prefill-energy axis, but the rendered legend is metric-aware (hwTypesWithData, InferenceContext.tsx) and intentionally drops coverage-filtered series — the assertion failed deterministically. Fixed to assert not.contain.text('B300'), and the test now also proves the switch is non-destructive by re-selecting Measured Joules per Output Token and asserting the b300 dots and legend entry return.
  2. Resolved: the spec had initially been pushed unexecuted. It has now been run locally (portable Node 22.14.0 + E2E_FIXTURES=1 dev server): 3/3 passing, plus adjacent regression specs measured-power-overlay / yaxis-metrics-render / ttft-x-axis-toggle / certified-power-filter69/69 passing.
  3. Resolved: stray Next.js-generated markdown files under packages/app (dev-server byproducts, regenerated on every next dev) removed from the worktree; never part of this diff.
  4. Documented: the avg_mem_used_mb → GiB /1024 conversion assumes MiB semantics (nvidia-smi convention); noted in code for re-check when the feat/agentx-power-artifacts producer lands.
  5. No change: the spec's exact dot-count assertion (have.length, 3) is stricter than sibling specs' at.least. Kept: the fixture injects exactly 3 disagg rows, hidden points are hidden via opacity (not DOM removal), and the assertion passed locally on the first attempt; relax only if CI proves it flaky.

Validation

  • bun run typecheckbun run lintbun run fmt
  • packages/app full unit suite: 251 files / 4499 tests passed ✅ (targeted power/registry/tooltip suites: 456 tests ✅)
  • packages/db (596 tests) and packages/constants (47 tests) ✅ (packages/mcp src/server.test.ts fails identically on the base branch — pre-existing environment issue, untouched here)
  • Cypress (local, Node 22.14.0 + E2E_FIXTURES=1): new agentic-measured-power.cy.ts 3/3 ✅; adjacent measured-power-overlay + yaxis-metrics-render + ttft-x-axis-toggle + certified-power-filter 69/69 ✅. CI re-verifies via tests-e2e.yml (specPattern + cypress-split, timings.json entry added).
  • Manual smoke screenshots to be attached from the PR preview once CI e2e is green.

Note

Low Risk
Frontend-only inference chart and tooltip changes with broad test coverage; tooltip HTML escapes JSONB worker fields to limit XSS risk.

Overview
Adds Measured Prefill/Decode J per token y-axes so disaggregated runs can plot role-local energy efficiency, not only whole-deployment J/tok and role watts. The metrics join the Measured Energy group (tier ring, tooltip tier line, Pareto lower-is-better) and map from prefill_joules_per_input_token / decode_joules_per_output_token in buildMeasuredPowerChartFields; series without those fields drop from the chart and legend until you pick an axis both carry.

Pinned chart tooltips now show a Measured Worker Power table when workers[] is present (role, chips, W, optional temp/util/mem/hosts), with HTML escaping, an 8-row cap, and EN/ZH strings across official, overlay, and GPU-compare tooltips; hover and empty workers stay unchanged.

Coverage includes Cypress agentic-measured-power (intercepted agentic benchmarks + fixtures), plus unit tests for transform, chart-utils, registry, tooltip drilldown, and Pareto direction.

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

…rilldown | 推理图表:新增角色级实测能耗坐标轴与每 worker 功耗下钻

Add y_measuredPrefillJPerInputToken and y_measuredDecodeJPerOutputToken to
the Measured Energy group (bilingual labels, lower polarity, explanations),
map prefill_joules_per_input_token / decode_joules_per_output_token into
chart fields, and render a pinned-only per-worker power table (role, chips,
watts, optional temp/util/mem/hosts, 8-row cap, HTML-escaped JSONB strings)
in all three tooltip generators.

在“实测能耗”分组新增每输入 token 实测 Prefill 能耗与每输出 token 实测
Decode 能耗两个 Y 轴(双语标签、越低越好、附说明),将角色级能耗字段映射
为图表字段,并在三种工具提示中新增仅固定时显示的每 worker 功耗表
(角色、芯片数、功耗,可选温度/利用率/显存/主机,上限 8 行,HTML 转义)。
…worker drilldown | 测试:覆盖 AgentX 实测功耗、角色级能耗坐标轴与 worker 下钻

Agentic-traces transform pass-through and power_valid=0 scrub (including
workers), role-energy chart-field emission/absence/zero-preservation,
Pareto corners and bilingual labels for the two new axes, input/output
token classification and share-link round-trip, and the pinned worker
table across all three tooltip generators (cap, ZH, HTML escaping).

覆盖 agentic_traces 变换透传与 power_valid=0 清除(含 workers)、角色级
能耗图表字段的生成/缺省/零值保留、两个新坐标轴的帕累托方向与双语标签、
输入/输出 token 分类与分享链接解析回环,以及三种工具提示中固定状态下
的 worker 表(行数上限、中文、HTML 转义)。
…使用合成 worker 数据的 AgentX 实测功耗

Spec-scoped intercepts inject a disagg agentic series (role energy +
workers[]) and an aggregate series (whole-run metrics, workers: null):
the two role-energy axes render under Measured Energy, the aggregate
series coverage-filters off while the legend stays intact, the pinned
tooltip shows the worker table, and a workers-less point stays clean.

通过 spec 级拦截注入分离式 agentic 序列(角色级能耗 + workers[])与
聚合序列(全程指标、workers 为 null):两个角色级能耗坐标轴显示在
“实测能耗”分组下,聚合序列因无该指标从图中过滤但图例保持完整,
固定的工具提示显示 worker 表,无 workers 的点保持正常。
…n-destructive axis switch | 端到端测试修复:断言覆盖过滤会移出图例,并验证坐标轴切换可无损恢复

The rendered legend is metric-aware (hwTypesWithData), so a series without
the selected role-energy metric drops out of the legend too — the previous
assertion expected it to stay and failed deterministically. Re-selecting a
whole-run measured axis now proves the selection universe survives the
switch. Also documents the MiB reading of avg_mem_used_mb in the worker
tooltip.

图例渲染依据所选指标(hwTypesWithData),缺少角色级能耗指标的系列会同时从图例中
移除,原断言必然失败。现改为断言其不在图例中,并通过重新选择全局实测坐标轴验证
选择集在切换后无损恢复。同时为 worker 工具提示中 avg_mem_used_mb 的 MiB 语义
补充说明。
@edwingao28
edwingao28 requested a review from adibarra as a code owner August 28, 2026 02:53
@vercel

vercel Bot commented Aug 28, 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 28, 2026 2:54am

Request Review

@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

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


Review: PR #935 — role-local measured energy axes + per-worker power drilldown

  • Read the diff against feat/measured-power-tiering
  • Review new registry axes + chart field mapping
  • Review generateWorkerPowerHTML (escaping, unit conversion, caps)
  • Review coverage filtering / legend behavior
  • Check test coverage
  • Check CI status
  • Runtime verification (Playwright)

🟢 LGTM — no blocking issues found

Static review + runtime verification both clean. This is a tight, well-scoped, well-tested addition.

What I verified statically

  • Registry wiring — both new keys (measuredPrefillJPerInputToken, measuredDecodeJPerOutputToken) carry bilingual labels/titles, polarity: 'lower', METRIC_EXPLANATIONS entries with the tier note, and membership in MEASURED_ENERGY_METRIC_CONFIG_KEYS (so the tier ring / tooltip line / footer key apply automatically). tokenMetricTypeForConfigKey correctly classifies them input/output via substring — no output substring collision on the prefill key.
  • Transform — role-local energy and workers[] are gated on measuredPowerValid (benchmark-transform.ts:237-242, :253), so power_valid: 0 scrubs them consistently with the role-watts fields. Gating role-local joules on power_valid only (not hasWholeDeploymentEnergySemantics) is correct — the role-prefixed fields have unambiguous role-local semantics and never had the whole-deployment drift problem. This is the shared transform, so the overlay/?unofficialrun= path is covered too.
  • Tooltip XSSw.role and w.hosts (artifact JSONB) are HTML-escaped before interpolation (escapeHtml, tooltipUtils.ts); the remaining interpolated values are typed numbers. isPinned is in scope in all three generators (official/overlay/GPU-compare) and correctly gates rendering; 8-row cap + +N more overflow logic is correct (no line at exactly 8).
  • avg_mem_used_mb / 1024 → GiB — the MiB-despite-_mb assumption is explicitly commented as a re-check item for PLAN-09's producer. Fine to ship as documented.

Runtime (Playwright, real read-only DB)

  • Both new options render under the Measured Energy group in the y-axis selector.
  • Selecting Measured Prefill Joules per Input Token produces no console errors; the chart coverage-filters to an empty state (the default agentic model in prod has no role-local energy yet — the expected graceful path). The synthetic Cypress fixtures exercise the populated render.

Tests — comprehensive: transform pass-through + scrub, chart-field emission/absence/zero-preservation, Pareto corners + bilingual labels, token-type classification + share-link round-trip, tooltip table across all three generators (pinned/unpinned, empty/absent, 8-row cap, ZH, HTML escaping), and a new AgentX e2e spec covering both axes, non-destructive coverage filtering, the worker drilldown, and graceful absence.

Note: stacked PR — base is feat/measured-power-tiering (#934), which must land first.
· feat/worker-power-drilldown

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