Skip to content

fix(power): withhold invalid and ambiguous measured energy / 屏蔽无效与语义不明的实测能耗 - #734

Open
edwingao28 wants to merge 2 commits into
masterfrom
fix/power-validity-guard
Open

fix(power): withhold invalid and ambiguous measured energy / 屏蔽无效与语义不明的实测能耗#734
edwingao28 wants to merge 2 commits into
masterfrom
fix/power-validity-guard

Conversation

@edwingao28

@edwingao28 edwingao28 commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

What

Two guards on measured power at the presentation boundary:

  1. power_valid = 0 is authoritative. An explicit invalid verdict scrubs every measured power/energy field instead of letting stale or unvalidated numbers render.
  2. Disaggregated rows need power_metric_schema_version = 2 before their unprefixed joules_per_* fields are shown. Role-prefixed fields (prefill_avg_power_w, decode_avg_power_w, prefill_joules_per_input_token, decode_joules_per_output_token) are unambiguous across versions and stay visible.

Legacy rows without a power_valid verdict remain eligible, so historical single-node measurements do not disappear.

Why

The unprefixed joules_per_* fields silently switched from role-local to whole-deployment energy when multinode aggregation landed. The values alone cannot distinguish the two, so a disaggregated row without the version stamp could be off by the prefill/decode split ratio with no way to detect it on the chart.

The producer-side stamp is InferenceX#2599. This PR is the consumer half.

Known, accepted regression

7 fixture/DB rows lose their 3 unprefixed J/token values: mori-sglang / mi355x / dsr1, disaggregated, no schema version.

These are vendor-supplied and we have no evidence of which energy convention they used. Their role watts still render — only the ambiguous unprefixed joules are withheld.

This is deliberate: a point that is wrong by several times, with no visual indication, is worse than a missing point. If the vendor confirms the convention, a follow-up can stamp the version at supplemental ingest and the rows come back.

Exact version match, not >=

power_metric_schema_version === 2, not >= 2. An open bound would silently admit a future schema whose semantics changed again — which is the exact failure that made versioning necessary. Bumping the producer constant now forces a deliberate change here too.

Verification

bun run typecheck   clean
bun run lint        clean
vitest benchmark-transform.test.ts   90 passed

Mutation-checked: reverting the guard to >= 2 fails exactly the new future-version test (1 failed | 89 passed).


Note

Medium Risk
Changes how disaggregated and invalid power rows render on charts (intentional withholding of ambiguous joules); ingest normalization affects stored power_valid/schema_version for malformed artifacts.

Overview
Adds a consumer-side power publication contract so charts do not show measured energy when the producer verdict or schema semantics are unclear.

ETL (benchmark-mapper) re-normalizes power_valid and power_metric_schema_version after generic numeric capture: only 1 / '1' counts as valid; malformed schema versions are dropped instead of loosely coerced.

Chart transform (rowToAggDataEntry) applies two guards:

  • power_valid === 0 scrubs all measured power/energy fields (including workers), while legacy rows without a verdict still show power.
  • Disaggregated rows only surface unprefixed joules_per_* when power_metric_schema_version === 2 (exact match, not >=). Role watts and explicit prefill_joules_* / decode_joules_* stay visible when power is valid.

Types and METRIC_KEYS document the new contract fields and version-2 whole-deployment vs role-local joules semantics.

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

@edwingao28
edwingao28 requested a review from adibarra as a code owner August 14, 2026 01:49
@vercel

vercel Bot commented Aug 14, 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 14, 2026 1:52am

Request Review

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

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


Review: withhold invalid and ambiguous measured energy

  • Read the diff (ac41a81..HEAD, 6 files)
  • Verify consumer guard (benchmark-transform.ts) and all downstream readers
  • Verify ETL normalization (benchmark-mapper.ts) and exact-version parsing
  • Check CI status (typecheck / unit / lint) and runtime render

Findings

The core guard is correct and well-reasoned, and the test coverage is thorough (both the consumer boundary and the ETL discriminators, including the mutation-check on >= 2 vs === 2):

  • The withholding logic sits at the single boundary rowToAggDataEntry, and every downstream reader (chart-utils.ts, useInterpolatedTrendData.ts) consumes the already-guarded AggDataEntry — no path bypasses it. ✅
  • measuredPowerValid = m.power_valid !== 0 correctly keeps legacy (undefined) rows eligible while treating an explicit 0 as authoritative, and scrubs avg_power_w, role watts, joules, and workers. ✅
  • Exact-version match (=== 2, not >= 2) is the right call and is guarded by a dedicated future-version test. ✅
  • normalizePowerContractMetrics fails closed: non-1/'1' verdicts → 0, and only canonical safe positive integers survive as the schema version. It runs before the rest of the mapper and the fields are untouched afterward. ✅
  • Runtime: /#inference renders real data with 0 console errors; typecheck / unit tests / lint all pass in CI. ✅

One 🟡 non-blocking item (inline on types.ts:155-156): the new prefill_joules_per_input_token / decode_joules_per_output_token are threaded through the transform but not wired to any Y-axis metric, so the "role-prefixed fields stay visible" claim in the PR body isn't reflected in the code — nothing renders them today. Not a regression on its own (they're new fields), but worth either wiring up or annotating as groundwork.

🟢 No blocking issues.
· fix/power-validity-guard

Comment on lines +155 to +156
prefill_joules_per_input_token?: number;
decode_joules_per_output_token?: number;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Non-blocking: These role-prefixed fields are threaded through rowToAggDataEntry but not consumed anywhere. The chart's only J/input axis (measuredJPerInputToken) reads the unprefixed entry.joules_per_input_token in both chart-utils.ts:472 and useInterpolatedTrendData.ts:92; there is no Y-axis metric wired to prefill_joules_per_input_token / decode_joules_per_output_token.

Why it matters: The PR description says "Role-prefixed fields … are unambiguous across versions and stay visible." As written they never become visible — a reviewer reading the diff would assume the disagg rows still show a role-local J/token point after their unprefixed value is withheld, but the chart surfaces nothing for them. That's a larger silent regression than the "7 rows" documented, since it hits every version-2 disagg row too.

Fix: If they're intended to render, wire a Y-axis metric (register in Y_AXIS_METRICS, createChartDataPoint, and ChartControls) so the role-local energy actually shows. If they're deliberate groundwork for a follow-up, a one-line comment saying so — and a note in the PR body — would avoid the "stay visible" claim being read as current behavior.

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

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


Re-review: withhold invalid and ambiguous measured energy

Focused on the new commit since my last review (db4422freject malformed power publication contract at ingest). The consumer half (4880c69) was already reviewed and passed.

  • Read new commit(s) since last review (4880c69..HEAD)
  • Verify ETL contract-normalization logic + tests
  • Check CI status (typecheck / unit / lint / e2e)
  • Runtime render check (/#inference, 0 console errors)

Findings

normalizePowerContractMetrics (benchmark-mapper.ts) is correct and fails closed — this is the right complement to the consumer guard:

  • power_valid1 only for 1/'1', else 0; absent when the row omits it. Booleans, 'garbage', NaN, Infinity all collapse to 0 (verified by the parametrized fails closed test). ✅
  • power_metric_schema_version accepts only canonical positive integers — the /^[1-9]\d*$/u string form plus a Number.isSafeInteger(v) && v > 0 check. It correctly rejects '02', '2.0', '2garbage', '1e2', ' 2', 1.5, -1, 0, and MAX_SAFE_INTEGER + 1. ✅
  • Ordering is safe: it runs after captureNumericMetrics (overwriting any loose coercion) and before preferFullResponseMetrics, which spreads {...metrics} and preserves both fields. ✅
  • Test coverage is thorough — discriminator matrix, fail-closed verdicts, and both malformed/accepted schema tables. ✅

CI: all green — typecheck / unit / lint / Component / E2E (chrome+firefox all shards) / Cursor Bugbot all pass.

Runtime: /#inference renders real data with 0 console errors.

Still open (previously noted, non-blocking)

The prefill_joules_per_input_token / decode_joules_per_output_token fields threaded onto AggDataEntry are still not wired to any Y-axis metric (see my earlier inline comment on types.ts:156). Not a regression on its own — carrying them forward is a prerequisite — but the PR body's "role-prefixed fields … stay visible" reads as current behavior when nothing renders them yet. A one-line "groundwork for a follow-up" note in the code/PR body would resolve the ambiguity.

🟢 LGTM — no blocking issues

The ETL guard is well-reasoned, fails closed, and is comprehensively tested. Nothing new to block on.
· fix/power-validity-guard

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