Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ Some routes serve a purpose that raw DB rows cannot satisfy:
- `/api/v1/overview` is a page-owned backend-for-frontend (BFF). It returns the compact `OverviewPageData` shape used by the initial server render, allowing selector changes to update the matrix without downloading every model's raw benchmark history or triggering a React Server Component (RSC) round trip. It is not a reusable public data API.
- `/api/v1/benchmarks?view=calculator&sequence=...` is a page-owned compact view. It returns only the selected calculator scenario and the metric keys needed for interpolation, while the default endpoint preserves the raw-row contract. This reduces transfer and browser parsing without narrowing the reusable inference response.

Two rules keep that BFF honest, both enforced in `overview-navigation.tsx`:
Three rules keep that BFF honest, all enforced in `overview-navigation.tsx`:

- **One cache key per data state.** Requests and the in-memory cache are keyed on a canonical href rebuilt from the resolved params (`overviewDataKey`), not the address bar. Explicit defaults, reordered params and campaign tags collapse to one key, so the CDN's day-long `s-maxage` is not fragmented by link variants.
- **`ref` never reaches that key.** The reference hardware only chooses which column the percentages are measured against, and every cost that needs is already in the payload, so the client derives it from the URL and recomputes the ratio per row. The server still resolves `ref` for SSR, shared links and no-JS.
- **Visited states expire after five minutes.** Visible matrices check freshness every 30 seconds and on focus/visibility changes. Background refreshes share in-flight requests and cannot overwrite a newer selector navigation or server payload. They do not change history or focus. The server caches remain invalidated by ingestion; the client TTL bounds how long an open tab can hide that invalidation.

Because the selector commit uses `History.prototype.pushState` rather than Next's patched version, `useSearchParams()` and `usePathname()` stay at the load-time URL on `/overview`. Anything that needs the live URL there listens for `CLIENT_SEARCH_CHANGE_EVENT`; the provider emits its own `$pageview`. Do not add a `useSearchParams()` consumer to the overview tree.

Expand Down
3 changes: 3 additions & 0 deletions docs/data-pipeline.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Data Pipeline Design

For the overview denominator, minimum-SLO selection, measurement dates, and GPU
count repair procedure, see [Overview data integrity](./overview-data-integrity.md).

## DB Schema Decisions

### Why Metrics Stay in JSONB
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Design rationale and non-obvious conventions. See [CLAUDE.md](../CLAUDE.md) for

- [Architecture](./architecture.md) — Why client-first, route navigation, URL state, provider nesting, server-side caching (unstable_cache + blob), in-memory client cache, color system, analytics enforcement
- [D3 Charts](./d3-charts.md) — Why 4 effects, in-place mutation, refs for zoom, rAF throttling, HTML tooltips, Pareto directions, gradient labels
- [Overview Data Integrity](./overview-data-integrity.md) — Verified throughput contract, minimum-SLO selection, date provenance, refresh behavior, and safe repair procedure
- [Data Pipeline](./data-pipeline.md) — DB schema reasoning, ETL design, transform pipeline, spline method choice, normalizer resolution order (model/GPU/framework)
- [Pitfalls](./pitfalls.md) — Failure modes: token type consistency, schema evolution, empty objects, zoom loss, stale closures, disaggregated metrics, negative splines, date stamping, ref stability, cost inheritance
- [GPU Specs](./gpu-specs.md) — Unit conventions, topology invariants, SVG layout rationale, hardware gotchas
Expand Down
117 changes: 117 additions & 0 deletions docs/overview-data-integrity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Overview data contract and repairs

The September 10, 2026 audit traced `/overview` from the main
`SemiAnalysisAI/InferenceX` producer through ETL, stored rows, API reads and the
page's serving frontiers. The app baseline was `9752d602`; the producer baseline
was `21bec226`. The producer's total-throughput arithmetic was correct. The
overview applied a second disaggregation penalty and hid useful measurements.

## Cost and coverage

`cost_per_million_total_tokens = gpu_hour_cost * 1_000_000 / (3600 * tput_per_gpu)`.
The rate is the hardware registry's hyperscaler GPU-hour assumption, not a
measured invoice. Input tokens include cached input. These are workload-specific
total-token costs, not output-token prices or a workload-independent ranking.

The producer already divides total throughput by all physical deployment chips.
Do not multiply it by `decode / (prefill + decode)` again. Fixed-sequence
disaggregated input/output fields have role-specific denominators; AgentX's
three throughput fields all use the entire deployment. See the producer's
[fixed-sequence aggregation](https://github.com/SemiAnalysisAI/InferenceX/blob/21bec22689a8f8274f50123ff11c72a6c5e36410/infx/results/fixed_sequence.py)
and [AgentX aggregation](https://github.com/SemiAnalysisAI/InferenceX/blob/21bec22689a8f8274f50123ff11c72a6c5e36410/utils/agentic/aggregation/process_agentic_result.py).

An independent request-profile calculation counted 3,603,566,231 input tokens
plus 26,257,390 output tokens over 3,629.862174258 seconds on 12 GPUs:
83,332.44823870831 total tok/s/GPU. At $2.31/GPU-hour this is
$0.00770008178 per million total tokens at that measured knot. The erroneous
8/12 factor raised it to $0.01155012267. The knot is above 50 tok/s/user;
the interpolated tier value is a separate calculation. Regression tests cover
both a source-derived AgentX observation and changing fixed-sequence topologies.

Each hardware column now selects the lowest cost across eligible FP4/FP8 and
speculative/standard decoding serving series. It retains engine, model, scenario,
offload, release and snapshot boundaries. The chosen tier is a **minimum SLO**:
an observed endpoint above it qualifies at its observed throughput, with a visible
actual-speed label. Interpolation remains within each measured Pareto frontier;
there is no extrapolation. A platform whose eligible measurements all fall below
the SLO remains missing. Current and historical snapshots use the same policy.
The overview's curated hardware/model/scenario scopes remain intentional; it has
no unofficial-run overlay. Shared ingestion fixes also reach unofficial imports.

## Dates and refresh

The cost evidence link labels measurement completion when a retained profile
provides it, otherwise the workflow-run attempt date with “measurement date
unavailable.” Its logical curve snapshot is labeled separately and still pins
the source-dashboard link. A reused profile in the audit ran August 12–13,
belonged to an August 14 attempt, and appeared in an August 25 curve snapshot.
Those dates are not interchangeable. A curve with incomplete measurement-date
coverage uses the consistently labeled run-date domain for its evidence.

Profile ingest records optional `measurement_start_unix_seconds` and
`measurement_end_unix_seconds` from successful profiling requests, including
one-token requests and excluding warmup/errors. The existing full-response
backfill fills missing dates without replacing already populated timing metrics
unless `--force` is used. Aggregate re-ingestion preserves attached-profile dates.

The browser's visited-state cache expires after five minutes. Visible tabs check
every 30 seconds and on focus; stale responses cannot replace a newer selection
or repopulate a cache invalidated by fresh server props. Background refreshes
preserve history and focus. The overview, /run and /rankings derived server cache keys are bumped; normal
ingestion still invalidates the server's DB caches. A client refresh does not
repair failed ingestion or an uninvalidated server cache.

## GPU topology and rollout

For identified single-node InferenceX producers, physical chips are
`TP * PP * PCP`; EP partitions TP devices and does not multiply them. Future
fixed-sequence ingest now follows the same contract as AgentX, preserving
explicit physical counts. One archived Dynamo artifact labeled a complete
8-GPU colocated result as `disagg=true, 8P+0D`; its input plus output throughput
matches total throughput. ETL recognizes this complete zero-decode shape and
mirrors its single pool into the schema's two role columns with `disagg=false`.
This does not change the stored throughput.

The audit found 105 current AgentX rows whose stored counts disagreed with their
independent total/per-GPU throughput ratio. Updating code does not repair these
existing rows. Run the following **read-only plan** first:

```bash
bun run --cwd packages/db db:backfill-benchmark-topology
```

The September 10 read-only dry run selected 225 rows including history: 223 AgentX count
repairs and two zero-decode classifications. It repairs only the exact legacy
TP×EP fallback where AgentX's aggregate ratio confirms TP×PP×PCP, plus the complete
zero-decode shape. Manual/proprietary deployments, TPUs, unknown shapes, explicit
nonlegacy counts, and fixed-sequence counts without independent evidence are
left alone. Old fixed-sequence counts require source-artifact re-ingestion;
the tool does not infer their original producer contract from dimensions alone.

After an operator reviews the plan, append `--apply` (interactive confirmation)
or `--apply --yes` (unattended). Apply uses one transaction, checks that the
planned row's config ID and metrics have not changed, and aborts on a uniqueness
conflict. It changes benchmark config references, retaining result IDs, metrics,
logs and replay links; shared config records and evaluations are not rewritten.
It refreshes `latest_benchmarks` after commit. If that refresh fails, the repair
is already committed: rerun the materialized-view refresh before verification.

Separately, the existing command
`bun run --cwd packages/db db:backfill-full-response-interactivity` fills retained
profile dates. It prompts before writing; `--limit N` caps rows actually updated,
scanning past profiles with no recoverable metrics or timestamps. Such profiles
remain eligible for a future retry if their retained content changes, but cannot
crowd recoverable rows out of a limited batch. No placeholder dates are written.
Do not use `--force` merely to add dates. After either repair, invalidate the
website DB cache using the existing admin cache command, then compare the affected
raw rows and overview values. The audit and PR preparation do not apply these
production database writes.

## Validation

An independent Python implementation of Pareto filtering, Steffen slopes, cubic
Hermite interpolation, configuration selection and cost arithmetic matched all
4,200 checked cells across the captured current snapshot and four historical
snapshots, six SLO tiers, both engine scopes, and the expanded model set. Unit
regressions additionally cover UTC measurement boundaries, refresh races,
unofficial artifact normalization and dry-run/transaction-conflict behavior.
29 changes: 15 additions & 14 deletions packages/app/cypress/e2e/agentic-point-coach-mark.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,19 @@ describe('Agentic point coach mark', () => {
// highlight ring sits exactly on the anchor (the pointer line deliberately
// stops short so its arrowhead doesn't cover the dot).
cy.get('[data-testid="agentic-point-coach-mark-pointer"]').should('exist');
cy.get('[data-testid="agentic-point-coach-mark-target"]').then(($ring) => {
// D3 transitions and the coach mark's animation-frame callback can still
// be moving. Retry a single, same-frame measurement of both coordinates;
// separate `.then()` commands compare positions from different frames.
cy.get('[data-testid="agentic-point-coach-mark-target"]').should(($ring) => {
const tipX = Number($ring.attr('cx'));
const tipY = Number($ring.attr('cy'));

cy.get(AGENTIC_MARKERS).then(($points) => {
const hit = [...$points].some((point) => {
const { x, y } = centreOf(point);
return Math.abs(x - tipX) < 1 && Math.abs(y - tipY) < 1;
});
expect(hit, 'pointer ends on an agentic point').to.eq(true);
const points = $ring[0].ownerDocument.querySelectorAll(AGENTIC_MARKERS);
const hit = [...points].some((point) => {
const { x, y } = centreOf(point);
return Math.abs(x - tipX) < 1 && Math.abs(y - tipY) < 1;
});
expect(hit, 'pointer ends on an agentic point').to.eq(true);
});
});

Expand Down Expand Up @@ -247,20 +249,19 @@ describe('Agentic point coach mark', () => {
);

cy.get(COACH_MARK).should('be.visible');
cy.get('[data-testid="agentic-point-coach-mark-target"]').then(($ring) => {
cy.get('[data-testid="agentic-point-coach-mark-target"]').should(($ring) => {
const tipX = Number($ring.attr('cx'));
const tipY = Number($ring.attr('cy'));
const onPoint = (element: Element) => {
const { x, y } = centreOf(element);
return Math.abs(x - tipX) < 1 && Math.abs(y - tipY) < 1;
};

cy.get('[data-testid="scatter-graph"] .unofficial-overlay-pt').then(($overlay) => {
expect([...$overlay].some(onPoint), 'pointer avoids overlay markers').to.eq(false);
});
cy.get(AGENTIC_MARKERS).then(($official) => {
expect([...$official].some(onPoint), 'pointer lands on an official point').to.eq(true);
});
const doc = $ring[0].ownerDocument;
const overlay = doc.querySelectorAll('[data-testid="scatter-graph"] .unofficial-overlay-pt');
const official = doc.querySelectorAll(AGENTIC_MARKERS);
expect([...overlay].some(onPoint), 'pointer avoids overlay markers').to.eq(false);
expect([...official].some(onPoint), 'pointer lands on an official point').to.eq(true);
});
});

Expand Down
Loading