diff --git a/.github/workflows/claude-chrome.yml b/.github/workflows/claude-chrome.yml
index f31fac8..3c1f041 100644
--- a/.github/workflows/claude-chrome.yml
+++ b/.github/workflows/claude-chrome.yml
@@ -206,7 +206,7 @@ jobs:
Data pipeline: Neon PostgreSQL → API routes (`/api/v1/*`) → React Query hooks (`src/hooks/api/`) → Context providers → D3 charts.
- DB layer: `packages/db/` (schema, migrations, ETL, queries)
- - API routes: `packages/app/src/app/api/v1/` (benchmarks, availability, workflow-info, reliability, evaluations, github-stars, invalidate)
+ - API routes: `packages/app/src/app/api/v1/` (benchmarks, availability, workflow-info, reliability, evaluations, invalidate)
- React Query hooks: `packages/app/src/hooks/api/` (use-benchmarks, use-availability, use-workflow-info, etc.)
- UI state lives in per-section Context providers (InferenceChartContext, EvaluationChartContext, ReliabilityChartContext), rendering in D3 components.
@@ -229,7 +229,6 @@ jobs:
├── workflow-info/route.ts # Workflow runs, changelogs, configs
├── reliability/route.ts # Reliability data
├── evaluations/route.ts # Evaluation data
- ├── github-stars/route.ts # GitHub star count
├── invalidate/route.ts # Cache invalidation (admin)
└── server-log/route.ts # Client error logging
@@ -240,7 +239,6 @@ jobs:
├── use-workflow-info.ts
├── use-evaluations.ts
├── use-reliability.ts
- └── use-github-stars.ts
packages/app/src/components/
├── page-content.tsx # Tab layout: VALID_TABS, desktop TabsTrigger + mobile Select
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
index 37d4307..93d9065 100644
--- a/.github/workflows/claude.yml
+++ b/.github/workflows/claude.yml
@@ -199,7 +199,7 @@ jobs:
Data pipeline: Neon PostgreSQL → API routes (`/api/v1/*`) → React Query hooks (`src/hooks/api/`) → Context providers → D3 charts.
- DB layer: `packages/db/` (schema, migrations, ETL, queries)
- - API routes: `packages/app/src/app/api/v1/` (benchmarks, availability, workflow-info, reliability, evaluations, github-stars, invalidate)
+ - API routes: `packages/app/src/app/api/v1/` (benchmarks, availability, workflow-info, reliability, evaluations, invalidate)
- React Query hooks: `packages/app/src/hooks/api/` (use-benchmarks, use-availability, use-workflow-info, etc.)
- UI state lives in per-section Context providers (InferenceChartContext, EvaluationChartContext, ReliabilityChartContext), rendering in D3 components.
@@ -222,7 +222,6 @@ jobs:
├── workflow-info/route.ts # Workflow runs, changelogs, configs
├── reliability/route.ts # Reliability data
├── evaluations/route.ts # Evaluation data
- ├── github-stars/route.ts # GitHub star count
├── invalidate/route.ts # Cache invalidation (admin)
└── server-log/route.ts # Client error logging
@@ -233,7 +232,6 @@ jobs:
├── use-workflow-info.ts
├── use-evaluations.ts
├── use-reliability.ts
- └── use-github-stars.ts
packages/app/src/components/
├── page-content.tsx # Tab layout: VALID_TABS, desktop TabsTrigger + mobile Select
diff --git a/.vscode/settings.json b/.vscode/settings.json
index b43223d..c824bfe 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -42,6 +42,7 @@
"llms",
"maxage",
"minimaxm",
+ "modelcontextprotocol",
"mooncake",
"moonshotai",
"mori",
@@ -55,16 +56,20 @@
"pageleave",
"postbuild",
"posthog",
+ "powerx",
"protobufjs",
"qwen",
"renderable",
"semianalysis",
"semianalysisai",
"sglang",
+ "shiki",
+ "shikijs",
"slurm",
"smallint",
"smallserial",
"timestamptz",
+ "togglable",
"tput",
"trtllm",
"ttft",
diff --git a/AGENTS.md b/AGENTS.md
index 67c1404..a7e0220 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -63,7 +63,6 @@ API routes (`packages/app/src/app/api/v1/`):
- `reliability` — raw `ReliabilityRow[]`
- `evaluations` — raw `EvalRow[]`
- `server-log` — retrieve benchmark runtime logs
-- `github-stars` — star count for the repo
- `invalidate` — invalidate API cache (admin)
**API routes return raw DB data** — no presentation logic. Frontend handles all transformations.
diff --git a/README.md b/README.md
index 22facd5..2040572 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ A [Next.js](https://nextjs.org) dashboard for visualizing ML inference benchmark
## Overview
-LLM inference performance is a major concern of providing AI services, but accurate performance analysis remains elusive. Fast cadence of software development and model releases makes comparing performance between setups difficult. Existing performance benchmarks quickly become obsolete due to being static, and participants game the benchmarks with unrealistic, highly specific configurations. InferenceX (formerly InferenceMAX) tackles these issues by benchmarking popular models on major hardware platforms nightly with the latest software. For each model and hardware combination, InferenceX sweeps through different tensor parallel sizes and max concurrent requests, showing the throughput vs. latency graph for the full picture. In terms of software configurations, we ensure they are generally applicable across different serving scenarios, and we open source the repo to welcome community contributions. We hope InferenceX informs the community up-to-date and realistic LLM inference performance.
+LLM inference performance is a major concern of providing AI services, but accurate performance analysis remains elusive. Fast cadence of software development and model releases makes comparing performance between setups difficult. Existing performance benchmarks quickly become obsolete due to being static, and participants game the benchmarks with unrealistic, highly specific configurations. InferenceX tackles these issues by benchmarking popular models on major hardware platforms nightly with the latest software. For each model and hardware combination, InferenceX sweeps through different tensor parallel sizes and max concurrent requests, showing the throughput vs. latency graph for the full picture. In terms of software configurations, we ensure they are generally applicable across different serving scenarios, and we open source the repo to welcome community contributions. We hope InferenceX informs the community up-to-date and realistic LLM inference performance.
## Architecture
diff --git a/package.json b/package.json
index f5a4e0a..33d2ad2 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"clean": "pnpm -r --parallel clean",
"clean:all": "pnpm -r --parallel clean:all && rimraf node_modules \"packages/*/node_modules\"",
"dev": "pnpm --filter *app --color dev",
+ "mcp": "pnpm --filter *mcp --color mcp",
"build": "pnpm --filter *app --color build",
"start": "pnpm --filter *app --color start",
"preview": "pnpm --filter *app --color preview",
diff --git a/packages/app/cypress/component/chart-legend.cy.tsx b/packages/app/cypress/component/chart-legend.cy.tsx
index d1310a9..4a362c2 100644
--- a/packages/app/cypress/component/chart-legend.cy.tsx
+++ b/packages/app/cypress/component/chart-legend.cy.tsx
@@ -58,9 +58,11 @@ function ChartLegendWrapper({ items = MOCK_ITEMS }: { items?: CommonLegendItemPr
isLegendExpanded={expanded}
onExpandedChange={setExpanded}
variant="sidebar"
- showResetFilter={itemsWithHandler.some((i) => !i.isActive)}
- allSelected={itemsWithHandler.every((i) => i.isActive)}
- onResetFilter={() => setLegendItems(items)}
+ actions={
+ itemsWithHandler.some((i) => !i.isActive)
+ ? [{ id: 'reset-filter', label: 'Reset filter', onClick: () => setLegendItems(items) }]
+ : []
+ }
/>
);
}
diff --git a/packages/app/cypress/component/favorite-presets.cy.tsx b/packages/app/cypress/component/favorite-presets.cy.tsx
deleted file mode 100644
index 4745a16..0000000
--- a/packages/app/cypress/component/favorite-presets.cy.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import FavoritePresetsDropdown from '@/components/favorites/FavoritePresetsDropdown';
-import { FAVORITE_PRESETS } from '@/components/favorites/favorite-presets';
-import { mountWithProviders } from '../support/test-utils';
-
-describe('FavoritePresetsDropdown', () => {
- beforeEach(() => {
- mountWithProviders(, { inference: {} });
- });
-
- it('renders the toggle button', () => {
- cy.get('[data-testid="favorites-toggle"]').should('be.visible');
- cy.get('[data-testid="favorites-toggle"]').should('contain.text', 'Favorites');
- });
-
- it('panel is hidden by default', () => {
- cy.get('[data-testid="favorites-panel"]').should('not.exist');
- });
-
- it('clicking toggle opens the panel', () => {
- cy.get('[data-testid="favorites-toggle"]').click();
- cy.get('[data-testid="favorites-panel"]').should('be.visible');
- });
-
- it('clicking toggle again closes the panel', () => {
- cy.get('[data-testid="favorites-toggle"]').click();
- cy.get('[data-testid="favorites-panel"]').should('be.visible');
- cy.get('[data-testid="favorites-toggle"]').click();
- cy.get('[data-testid="favorites-panel"]').should('not.exist');
- });
-
- it('renders all preset cards with their titles', () => {
- cy.get('[data-testid="favorites-toggle"]').click();
-
- for (const preset of FAVORITE_PRESETS) {
- cy.get(`[data-testid="favorite-preset-${preset.id}"]`)
- .should('be.visible')
- .and('contain.text', preset.title);
- }
- });
-
- it('preset cards show descriptions and tags', () => {
- cy.get('[data-testid="favorites-toggle"]').click();
-
- const first = FAVORITE_PRESETS[0];
- cy.get(`[data-testid="favorite-preset-${first.id}"]`).should(
- 'contain.text',
- first.description.slice(0, 30),
- );
-
- for (const tag of first.tags) {
- cy.get(`[data-testid="favorite-preset-${first.id}"]`).should('contain.text', tag);
- }
- });
-
- it('clicking a preset calls context setters to apply it', () => {
- cy.get('[data-testid="favorites-toggle"]').click();
-
- const preset = FAVORITE_PRESETS[0]; // gb200-vs-b200
- cy.get(`[data-testid="favorite-preset-${preset.id}"]`).click();
-
- cy.get('@setSelectedModel').should('have.been.calledWith', preset.config.model);
- cy.get('@setSelectedSequence').should('have.been.calledWith', preset.config.sequence);
- cy.get('@setSelectedPrecisions').should('have.been.calledWith', preset.config.precisions);
- cy.get('@setSelectedYAxisMetric').should('have.been.calledWith', preset.config.yAxisMetric);
- cy.get('@setActivePresetId').should('have.been.calledWith', preset.id);
- });
-
- it('clicking an active preset clears it', () => {
- // Mount with the first preset already active
- mountWithProviders(, {
- inference: { activePresetId: FAVORITE_PRESETS[0].id },
- });
-
- cy.get('[data-testid="favorites-toggle"]').click();
- cy.get(`[data-testid="favorite-preset-${FAVORITE_PRESETS[0].id}"]`).click();
-
- // Clearing sets activePresetId to null
- cy.get('@setActivePresetId').should('have.been.calledWith', null);
- cy.get('@selectAllHwTypes').should('have.been.called');
- });
-
- it('shows "Active" badge when a preset is active', () => {
- mountWithProviders(, {
- inference: { activePresetId: FAVORITE_PRESETS[0].id },
- });
-
- cy.get('[data-testid="favorites-toggle"]').should('contain.text', FAVORITE_PRESETS[0].title);
- cy.get('[data-testid="favorites-toggle"]').should('contain.text', 'Active');
- });
-});
diff --git a/packages/app/cypress/component/header.cy.tsx b/packages/app/cypress/component/header.cy.tsx
index 827f2bc..321a9c3 100644
--- a/packages/app/cypress/component/header.cy.tsx
+++ b/packages/app/cypress/component/header.cy.tsx
@@ -28,7 +28,7 @@ describe('Header', () => {
it('shows Dashboard nav link', () => {
cy.get('[data-testid="nav-link-dashboard"]').should('be.visible');
- cy.get('[data-testid="nav-link-dashboard"]').should('have.attr', 'href', '/');
+ cy.get('[data-testid="nav-link-dashboard"]').should('have.attr', 'href', '/inference');
});
it('shows Supporters nav link', () => {
diff --git a/packages/app/cypress/e2e/csv-export.cy.ts b/packages/app/cypress/e2e/csv-export.cy.ts
index 15c1f74..e13ad20 100644
--- a/packages/app/cypress/e2e/csv-export.cy.ts
+++ b/packages/app/cypress/e2e/csv-export.cy.ts
@@ -3,7 +3,7 @@ describe('CSV Export', () => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
- cy.visit('/');
+ cy.visit('/inference');
cy.get('[data-testid="chart-figure"]').should('exist');
});
diff --git a/packages/app/cypress/e2e/custom-user-values.cy.ts b/packages/app/cypress/e2e/custom-user-values.cy.ts
index b47926d..6ae7200 100644
--- a/packages/app/cypress/e2e/custom-user-values.cy.ts
+++ b/packages/app/cypress/e2e/custom-user-values.cy.ts
@@ -3,22 +3,22 @@ describe('Custom User Values', () => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
- cy.visit('/');
+ cy.visit('/inference');
cy.get('[data-testid="model-selector"]').should('be.visible');
});
const selectCustomCostMetric = () => {
- cy.get('[data-testid="yaxis-metric-selector"]').click();
+ cy.get('[data-testid="yaxis-metric-selector"]').click({ force: true });
cy.get('[role="option"]')
.contains('Cost per Million Total Tokens (Custom User Values)')
- .click();
+ .click({ force: true });
};
const selectCustomPowerMetric = () => {
- cy.get('[data-testid="yaxis-metric-selector"]').click();
+ cy.get('[data-testid="yaxis-metric-selector"]').click({ force: true });
cy.get('[role="option"]')
.contains('Token Throughput per All in Utility MW (Custom User Values)')
- .click();
+ .click({ force: true });
};
describe('Custom GPU Costs', () => {
diff --git a/packages/app/cypress/e2e/drill-down-trend.cy.ts b/packages/app/cypress/e2e/drill-down-trend.cy.ts
index 9649290..bd06ec7 100644
--- a/packages/app/cypress/e2e/drill-down-trend.cy.ts
+++ b/packages/app/cypress/e2e/drill-down-trend.cy.ts
@@ -8,7 +8,7 @@ describe('Drill-Down Trend Chart Modal', () => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
- cy.visit('/');
+ cy.visit('/inference');
// Wait for scatter graph to render with data points
cy.get('[data-testid="scatter-graph"]')
.first()
diff --git a/packages/app/cypress/e2e/favorite-presets.cy.ts b/packages/app/cypress/e2e/favorite-presets.cy.ts
deleted file mode 100644
index 4c1bf44..0000000
--- a/packages/app/cypress/e2e/favorite-presets.cy.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-describe('Favorite Presets', () => {
- before(() => {
- cy.visit('/', {
- onBeforeLoad(win) {
- win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
- },
- });
- cy.get('[data-testid="model-selector"]').should('be.visible');
- });
-
- it('renders the favorites dropdown toggle', () => {
- cy.get('[data-testid="favorites-toggle"]').should('be.visible');
- cy.get('[data-testid="favorites-toggle"]').should('contain.text', 'Favorites');
- });
-
- it('opens the preset panel on click', () => {
- cy.get('[data-testid="favorites-toggle"]').click();
- cy.get('[data-testid="favorites-panel"]').should('be.visible');
- });
-
- it('shows all 6 preset cards', () => {
- cy.get('[data-testid^="favorite-preset-"]').should('have.length', 6);
- });
-
- it('each preset card has a title and description', () => {
- cy.get('[data-testid^="favorite-preset-"]').each(($card) => {
- cy.wrap($card).find('p').first().should('not.be.empty');
- cy.wrap($card).find('p').eq(1).should('not.be.empty');
- });
- });
-
- it('each preset card has at least one tag badge', () => {
- cy.get('[data-testid^="favorite-preset-"]').each(($card) => {
- cy.wrap($card).find('.flex-wrap').children().should('have.length.greaterThan', 0);
- });
- });
-
- // Scatter preset: b200-vs-h200
- it('activating b200-vs-h200 shows Active badge and renders data', () => {
- cy.get('[data-testid="favorite-preset-b200-vs-h200"]').click();
- cy.get('[data-testid="favorites-toggle"]').should('contain.text', 'B200 vs H200');
- cy.get('[data-testid="favorites-toggle"]').should('contain.text', 'Active');
-
- // Chart should render with data points (not empty)
- cy.get('[data-testid="scatter-graph"]').first().find('svg .dot-group').should('exist');
- cy.contains('No data available').should('not.exist');
- });
-
- // Deactivate preset
- it('clicking the active preset again deactivates it', () => {
- cy.get('[data-testid="favorite-preset-b200-vs-h200"]').click();
- cy.get('[data-testid="favorites-toggle"]').should('contain.text', 'Favorites');
- cy.get('[data-testid="favorites-toggle"]').should('not.contain.text', 'Active');
-
- // Chart should still render (not empty)
- cy.get('[data-testid="scatter-graph"]').first().find('svg').should('exist');
- });
-
- // Scatter preset: amd-generations
- it('activating amd-generations shows AMD preset and renders data', () => {
- cy.get('[data-testid="favorite-preset-amd-generations"]').click();
- cy.get('[data-testid="favorites-toggle"]').should('contain.text', 'AMD');
- cy.get('[data-testid="favorites-toggle"]').should('contain.text', 'Active');
-
- cy.get('[data-testid="scatter-graph"]').first().find('svg .dot-group').should('exist');
- cy.contains('No data available').should('not.exist');
- });
-
- // Switch directly to another preset
- it('switching from one preset to another updates the active state', () => {
- cy.get('[data-testid="favorite-preset-gb200-vs-b200"]').click();
- cy.get('[data-testid="favorites-toggle"]').should('contain.text', 'GB200');
- cy.get('[data-testid="favorites-toggle"]').should('contain.text', 'Active');
- });
-
- // Clean up: deactivate and close
- it('deactivate and close the panel', () => {
- cy.get('[data-testid="favorite-preset-gb200-vs-b200"]').click();
- cy.get('[data-testid="favorites-toggle"]').should('not.contain.text', 'Active');
- cy.get('[data-testid="favorites-toggle"]').click();
- cy.get('[data-testid="favorites-panel"]').should('not.exist');
- });
-});
diff --git a/packages/app/cypress/e2e/gpu-power.cy.ts b/packages/app/cypress/e2e/gpu-power.cy.ts
index a4ee52e..9991e5f 100644
--- a/packages/app/cypress/e2e/gpu-power.cy.ts
+++ b/packages/app/cypress/e2e/gpu-power.cy.ts
@@ -5,7 +5,7 @@ function unlockPowerX() {
describe('PowerX', () => {
beforeEach(() => {
- cy.visit('/', {
+ cy.visit('/inference', {
onBeforeLoad(win) {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
},
@@ -32,17 +32,17 @@ describe('PowerX', () => {
describe('(unlocked)', () => {
beforeEach(() => {
- cy.visit('/', {
+ cy.visit('/inference', {
onBeforeLoad(win) {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
- win.localStorage.setItem('inferencex-powerx-unlocked', '1');
+ win.localStorage.setItem('inferencex-feature-gate', '1');
},
});
});
it('clicking the gpu-metrics tab activates it and shows content', () => {
cy.get('[data-testid="tab-trigger-gpu-metrics"]').click();
- cy.get('[data-testid="tab-trigger-gpu-metrics"]').should('have.attr', 'data-state', 'active');
+ cy.url().should('include', '/gpu-metrics');
cy.get('[data-testid="gpu-metrics-display"]').find('h2').should('contain.text', 'PowerX');
});
diff --git a/packages/app/cypress/e2e/gpu-specs.cy.ts b/packages/app/cypress/e2e/gpu-specs.cy.ts
index 9d78bd1..92f8f9c 100644
--- a/packages/app/cypress/e2e/gpu-specs.cy.ts
+++ b/packages/app/cypress/e2e/gpu-specs.cy.ts
@@ -1,5 +1,8 @@
describe('GPU Specs Tab', () => {
before(() => {
+ cy.window().then((win) => {
+ win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
+ });
cy.visit('/gpu-specs');
// Wait for GPU Specs tab content to be present in the DOM
cy.get('h2').contains('GPU Specifications').should('exist');
@@ -206,6 +209,9 @@ describe('GPU Specs Tab', () => {
describe('GPU Specs Bar Chart View', () => {
before(() => {
+ cy.window().then((win) => {
+ win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
+ });
cy.visit('/gpu-specs');
cy.get('h2').contains('GPU Specifications').should('exist');
// Dismiss any Radix Dialog scroll locks from topology diagram components
@@ -277,6 +283,9 @@ describe('GPU Specs Bar Chart View', () => {
describe('GPU Specs Radar Chart View', () => {
before(() => {
+ cy.window().then((win) => {
+ win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
+ });
cy.visit('/gpu-specs');
cy.get('h2').contains('GPU Specifications').should('exist');
cy.get('body').then(($body) => {
@@ -370,12 +379,16 @@ describe('GPU Specs Radar Chart View', () => {
});
describe('GPU Specs Navigation', () => {
+ before(() => {
+ cy.window().then((win) => {
+ win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
+ });
+ cy.visit('/inference');
+ cy.get('[data-testid="chart-section-tabs"]').should('be.visible');
+ });
+
it('tab switcher activates GPU Specs', () => {
- cy.visit('/');
- // Wait for tabs to be rendered and page to be interactive
- cy.get('[role="tablist"]').should('be.visible');
- // Use force:true to handle potential pointer-events:none from modals/overlays
- cy.get('[role="tablist"]').contains('GPU Specs').click({ force: true });
+ cy.get('[data-testid="tab-trigger-gpu-specs"]').click();
cy.url().should('include', '/gpu-specs');
cy.get('h2').should('contain.text', 'GPU Specifications');
});
@@ -383,6 +396,9 @@ describe('GPU Specs Navigation', () => {
describe('Topology Dialog Navigation', () => {
before(() => {
+ cy.window().then((win) => {
+ win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
+ });
cy.visit('/gpu-specs');
cy.get('h2').contains('GPU Specifications').should('exist');
});
diff --git a/packages/app/cypress/e2e/gradient-labels.cy.ts b/packages/app/cypress/e2e/gradient-labels.cy.ts
index d41fec6..7a5efe5 100644
--- a/packages/app/cypress/e2e/gradient-labels.cy.ts
+++ b/packages/app/cypress/e2e/gradient-labels.cy.ts
@@ -1,6 +1,6 @@
describe('Gradient Labels Toggle', () => {
before(() => {
- cy.visit('/', {
+ cy.visit('/inference', {
onBeforeLoad(win) {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
},
@@ -78,7 +78,7 @@ describe('Gradient Labels Toggle', () => {
});
it('URL param i_gradlabel=1 enables gradient labels on load', () => {
- cy.visit('/?i_gradlabel=1', {
+ cy.visit('/inference?i_gradlabel=1', {
onBeforeLoad(win) {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
},
@@ -88,7 +88,7 @@ describe('Gradient Labels Toggle', () => {
});
it('URL param i_advlabel=1 enables parallelism labels on load', () => {
- cy.visit('/?i_advlabel=1', {
+ cy.visit('/inference?i_advlabel=1', {
onBeforeLoad(win) {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
},
@@ -104,8 +104,8 @@ describe('Gradient Labels with non-default Y-axis metrics', () => {
const selectMetricAndEnableGradient = (metricLabel: string) => {
// Switch to the target Y-axis metric
- cy.get('[data-testid="yaxis-metric-selector"]').click();
- cy.contains('[role="option"]', metricLabel).click();
+ cy.get('[data-testid="yaxis-metric-selector"]').click({ force: true });
+ cy.contains('[role="option"]', metricLabel).click({ force: true });
// Wait for chart to re-render with new metric
cy.get('[data-testid="scatter-graph"]').should('be.visible');
@@ -117,7 +117,7 @@ describe('Gradient Labels with non-default Y-axis metrics', () => {
};
before(() => {
- cy.visit('/', {
+ cy.visit('/inference', {
onBeforeLoad(win) {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
},
diff --git a/packages/app/cypress/e2e/high-contrast.cy.ts b/packages/app/cypress/e2e/high-contrast.cy.ts
index 211f724..2f726af 100644
--- a/packages/app/cypress/e2e/high-contrast.cy.ts
+++ b/packages/app/cypress/e2e/high-contrast.cy.ts
@@ -3,7 +3,7 @@ describe('High Contrast Mode', () => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
- cy.visit('/');
+ cy.visit('/inference');
cy.get('[data-testid="scatter-graph"]').should('exist');
});
@@ -16,7 +16,7 @@ describe('High Contrast Mode', () => {
});
it('visiting with i_hc=1 applies high contrast on load', () => {
- cy.visit('/?i_hc=1');
+ cy.visit('/inference?i_hc=1');
cy.get('[data-testid="scatter-graph"]').should('exist');
cy.get('#scatter-high-contrast').first().should('have.attr', 'data-state', 'checked');
});
@@ -42,7 +42,7 @@ describe('High Contrast Mode', () => {
});
it('multiple high contrast params can coexist in URL', () => {
- cy.visit('/?i_hc=1&r_hc=1&e_hc=1');
+ cy.visit('/inference?i_hc=1&r_hc=1&e_hc=1');
cy.get('[data-testid="scatter-graph"]').should('exist');
cy.get('#scatter-high-contrast').first().should('have.attr', 'data-state', 'checked');
});
diff --git a/packages/app/cypress/e2e/historical-trends.cy.ts b/packages/app/cypress/e2e/historical-trends.cy.ts
index a338acd..6b577de 100644
--- a/packages/app/cypress/e2e/historical-trends.cy.ts
+++ b/packages/app/cypress/e2e/historical-trends.cy.ts
@@ -69,13 +69,24 @@ describe('Historical Trends — Content & Interactions', () => {
});
it('model selector is present and has selectable options', () => {
+ // Clear any stale Radix scroll lock from prior Select interactions
+ cy.document().then((doc) => {
+ doc.body.removeAttribute('data-scroll-locked');
+ doc.body.style.removeProperty('pointer-events');
+ });
cy.get('[data-testid="model-selector"]').should('be.visible');
+ // Radix Select may need a brief settle after scroll lock removal
+ cy.wait(100);
cy.get('[data-testid="model-selector"]').click();
cy.get('[role="option"]').should('have.length.greaterThan', 0);
cy.get('body').type('{esc}');
});
it('sequence selector is present and has selectable options', () => {
+ cy.document().then((doc) => {
+ doc.body.removeAttribute('data-scroll-locked');
+ doc.body.style.removeProperty('pointer-events');
+ });
cy.get('[data-testid="sequence-selector"]').should('be.visible');
cy.get('[data-testid="sequence-selector"]').click();
cy.get('[role="option"]').should('have.length.greaterThan', 0);
@@ -95,6 +106,10 @@ describe('Historical Trends — Content & Interactions', () => {
});
it('Log Scale switch exists in the legend and can be toggled', () => {
+ cy.document().then((doc) => {
+ doc.body.removeAttribute('data-scroll-locked');
+ doc.body.style.removeProperty('pointer-events');
+ });
cy.get('[data-testid="historical-trend-figure"]')
.find('.sidebar-legend')
.contains('label', 'Log Scale')
@@ -112,6 +127,10 @@ describe('Historical Trends — Content & Interactions', () => {
});
it('Y-axis metric selector is present and can be changed', () => {
+ cy.document().then((doc) => {
+ doc.body.removeAttribute('data-scroll-locked');
+ doc.body.style.removeProperty('pointer-events');
+ });
cy.get('[data-testid="yaxis-metric-selector"]').should('be.visible');
cy.get('[data-testid="yaxis-metric-selector"]').click();
cy.get('[role="option"]').should('have.length.greaterThan', 1);
@@ -127,7 +146,12 @@ describe('Historical Trends — Content & Interactions', () => {
});
it('changing model updates the chart title to reflect the new model', () => {
+ cy.document().then((doc) => {
+ doc.body.removeAttribute('data-scroll-locked');
+ doc.body.style.removeProperty('pointer-events');
+ });
cy.get('[data-testid="historical-trend-figure"]').should('exist');
+ cy.wait(100);
cy.get('[data-testid="historical-trend-figure"] figcaption p')
.first()
diff --git a/packages/app/cypress/e2e/inference-chart.cy.ts b/packages/app/cypress/e2e/inference-chart.cy.ts
index e39cd83..93350d6 100644
--- a/packages/app/cypress/e2e/inference-chart.cy.ts
+++ b/packages/app/cypress/e2e/inference-chart.cy.ts
@@ -3,7 +3,7 @@ describe('Inference Chart', () => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
- cy.visit('/');
+ cy.visit('/inference');
});
it('renders the inference chart display wrapper', () => {
diff --git a/packages/app/cypress/e2e/model-architecture.cy.ts b/packages/app/cypress/e2e/model-architecture.cy.ts
index f143300..474446c 100644
--- a/packages/app/cypress/e2e/model-architecture.cy.ts
+++ b/packages/app/cypress/e2e/model-architecture.cy.ts
@@ -2,7 +2,7 @@ describe('Model Architecture Diagram', () => {
before(() => {
// Use desktop viewport to ensure all UI elements are visible
cy.viewport(1280, 800);
- cy.visit('/', {
+ cy.visit('/inference', {
onBeforeLoad(win) {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
},
@@ -101,6 +101,11 @@ describe('Model Architecture Diagram', () => {
describe('Collapsible Transformer Block (Dense model - Llama 3.3 70B)', () => {
before(() => {
// Switch model and open architecture
+ // Clear any stale Radix scroll lock from prior Select interactions
+ cy.document().then((doc) => {
+ doc.body.removeAttribute('data-scroll-locked');
+ doc.body.style.removeProperty('pointer-events');
+ });
cy.get('[role="combobox"]').filter(':visible').first().click();
cy.get('[role="option"]').contains('Llama 3.3').click();
@@ -139,6 +144,11 @@ describe('Model Architecture Diagram', () => {
describe('Collapsible Transformer Blocks (MoE model - Kimi K2.5)', () => {
before(() => {
+ // Clear any stale Radix scroll lock from prior Select interactions
+ cy.document().then((doc) => {
+ doc.body.removeAttribute('data-scroll-locked');
+ doc.body.style.removeProperty('pointer-events');
+ });
cy.get('[role="combobox"]').filter(':visible').first().click();
cy.get('[role="option"]').contains('Kimi K2.5').click();
@@ -182,6 +192,11 @@ describe('Model Architecture Diagram', () => {
describe('Collapsible Transformer Blocks (MoE model - MiniMax M2.5)', () => {
before(() => {
+ // Clear any stale Radix scroll lock from prior Select interactions
+ cy.document().then((doc) => {
+ doc.body.removeAttribute('data-scroll-locked');
+ doc.body.style.removeProperty('pointer-events');
+ });
cy.get('[role="combobox"]').filter(':visible').first().click();
cy.get('[role="option"]').contains('MiniMax M2.5').click();
@@ -225,6 +240,11 @@ describe('Model Architecture Diagram', () => {
describe('Alternating Attention Blocks (MoE model - gpt-oss 120B)', () => {
before(() => {
+ // Clear any stale Radix scroll lock from prior Select interactions
+ cy.document().then((doc) => {
+ doc.body.removeAttribute('data-scroll-locked');
+ doc.body.style.removeProperty('pointer-events');
+ });
cy.get('[role="combobox"]').filter(':visible').first().click();
cy.get('[role="option"]').contains('gpt-oss').click();
diff --git a/packages/app/cypress/e2e/performance.cy.ts b/packages/app/cypress/e2e/performance.cy.ts
index 99e89ac..2c9d57e 100644
--- a/packages/app/cypress/e2e/performance.cy.ts
+++ b/packages/app/cypress/e2e/performance.cy.ts
@@ -1,7 +1,7 @@
describe('Performance', () => {
it('page loads quickly', () => {
const startTime = Date.now();
- cy.visit('/');
+ cy.visit('/inference');
cy.document().then(() => {
const loadTime = Date.now() - startTime;
const threshold = 5_000;
@@ -18,7 +18,7 @@ describe('Performance', () => {
});
it('no excessive layout shift issues', () => {
- cy.visit('/');
+ cy.visit('/inference');
cy.wait(5000);
// Measure CLS using PerformanceObserver
diff --git a/packages/app/cypress/e2e/tabs.cy.ts b/packages/app/cypress/e2e/tabs.cy.ts
index 723f6e6..dbc0d15 100644
--- a/packages/app/cypress/e2e/tabs.cy.ts
+++ b/packages/app/cypress/e2e/tabs.cy.ts
@@ -3,7 +3,7 @@ describe('Chart Section Tabs — E2E', () => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
- cy.visit('/');
+ cy.visit('/inference');
});
it('updates the URL path when switching tabs', () => {
@@ -28,7 +28,7 @@ describe('Chart Section Tabs — E2E', () => {
it('shows mobile chart select dropdown on small viewport', () => {
cy.viewport(375, 812);
- cy.visit('/');
+ cy.visit('/inference');
cy.get('[data-testid="mobile-chart-select"]').should('be.visible');
});
});
diff --git a/packages/app/cypress/e2e/throughput-calculator.cy.ts b/packages/app/cypress/e2e/throughput-calculator.cy.ts
index b2d95d7..1baba2c 100644
--- a/packages/app/cypress/e2e/throughput-calculator.cy.ts
+++ b/packages/app/cypress/e2e/throughput-calculator.cy.ts
@@ -8,7 +8,7 @@ describe('TCO Calculator', () => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
- cy.visit('/');
+ cy.visit('/inference');
});
it('shows the TCO Calculator tab trigger', () => {
@@ -16,30 +16,16 @@ describe('TCO Calculator', () => {
cy.get('[data-testid="tab-trigger-calculator"]').should('contain.text', 'TCO Calculator');
});
- it('calculator tab is inactive by default', () => {
- cy.get('[data-testid="tab-trigger-calculator"]').should(
- 'have.attr',
- 'data-state',
- 'inactive',
- );
- });
-
- it('clicking the calculator tab activates it', () => {
+ it('clicking the calculator tab navigates to it', () => {
cy.get('[data-testid="tab-trigger-calculator"]').click();
- cy.get('[data-testid="tab-trigger-calculator"]').should('have.attr', 'data-state', 'active');
- cy.get('[data-testid="tab-trigger-inference"]').should('have.attr', 'data-state', 'inactive');
- });
-
- it('updates the URL path to /calculator', () => {
- // Already on calculator from previous test
cy.url().should('include', '/calculator');
});
it('switches back to inference tab and then returns to calculator', () => {
cy.get('[data-testid="tab-trigger-inference"]').click();
- cy.get('[data-testid="tab-trigger-inference"]').should('have.attr', 'data-state', 'active');
+ cy.url().should('include', '/inference');
cy.get('[data-testid="tab-trigger-calculator"]').click();
- cy.get('[data-testid="tab-trigger-calculator"]').should('have.attr', 'data-state', 'active');
+ cy.url().should('include', '/calculator');
cy.get('[data-testid="calculator-controls"]').should('be.visible');
});
});
@@ -315,10 +301,19 @@ describe('TCO Calculator', () => {
cy.get('[data-testid="calculator-bar-chart"] svg .bar').should('have.length.greaterThan', 0);
});
- it('model selector has selectable options', () => {
- cy.get('[data-testid="calculator-controls"]').within(() => {
- cy.get('#calc-model').click();
+ // Clear stale Radix scroll lock before each test to prevent pointer-events: none
+ beforeEach(() => {
+ cy.document().then((doc) => {
+ doc.body.removeAttribute('data-scroll-locked');
+ doc.body.style.removeProperty('pointer-events');
});
+ });
+
+ it('model selector has selectable options', () => {
+ // Wait for availability data to load — model selector value updates when data arrives
+ cy.get('#calc-model').should('not.contain.text', 'Model');
+ cy.wait(100);
+ cy.get('#calc-model').click();
cy.get('[role="option"]').should('have.length.greaterThan', 0);
cy.get('body').type('{esc}');
});
@@ -552,7 +547,7 @@ describe('TCO Calculator', () => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
cy.visit('/calculator');
- cy.get('[data-testid="tab-trigger-calculator"]').should('have.attr', 'data-state', 'active');
+ cy.url().should('include', '/calculator');
cy.get('[data-testid="calculator-controls"]').should('be.visible');
cy.get('[data-testid="calculator-bar-chart"] svg .bar').should('have.length.greaterThan', 0);
});
diff --git a/packages/app/cypress/e2e/ttft-x-axis-toggle.cy.ts b/packages/app/cypress/e2e/ttft-x-axis-toggle.cy.ts
index cfcfe69..e17a4af 100644
--- a/packages/app/cypress/e2e/ttft-x-axis-toggle.cy.ts
+++ b/packages/app/cypress/e2e/ttft-x-axis-toggle.cy.ts
@@ -3,7 +3,7 @@ describe('TTFT X-Axis Toggle (E2E chart)', () => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
- cy.visit('/');
+ cy.visit('/inference');
cy.get('[data-testid="chart-figure"]').should('have.length.at.least', 2);
});
diff --git a/packages/app/cypress/e2e/url-params.cy.ts b/packages/app/cypress/e2e/url-params.cy.ts
index ae1b71a..c940b9a 100644
--- a/packages/app/cypress/e2e/url-params.cy.ts
+++ b/packages/app/cypress/e2e/url-params.cy.ts
@@ -11,13 +11,13 @@ describe('URL Parameter Persistence', () => {
});
it('page loads without error with unknown params', () => {
- cy.visit('/?unknown_param=test');
+ cy.visit('/inference?unknown_param=test');
cy.get('[data-testid="inference-chart-display"]').should('exist');
});
describe('Inference legend', () => {
it('i_legend=0 collapses the sidebar legend on load', () => {
- cy.visit('/?i_legend=0');
+ cy.visit('/inference?i_legend=0');
cy.get('.sidebar-legend').first().should('be.visible');
cy.get('.sidebar-legend').first().should('not.have.class', 'bg-accent');
});
@@ -25,7 +25,7 @@ describe('URL Parameter Persistence', () => {
describe('Inference Y-axis metric', () => {
it('i_metric URL param pre-selects the metric and updates SVG axis label', () => {
- cy.visit('/?i_metric=y_costh');
+ cy.visit('/inference?i_metric=y_costh');
cy.get('[data-testid="yaxis-metric-selector"]').should(
'contain.text',
@@ -39,18 +39,17 @@ describe('URL Parameter Persistence', () => {
});
it('changing Y-axis metric via dropdown updates SVG axis label', () => {
- cy.visit('/');
+ cy.visit('/inference');
cy.get('[data-testid="scatter-graph"]')
.first()
.find('svg text[transform="rotate(-90)"]')
.should('contain.text', 'Throughput');
- cy.get('[data-testid="yaxis-metric-selector"]').click();
- cy.contains(
- '[role="option"]',
- 'Cost per Million Total Tokens (Owning - Hyperscaler)',
- ).click();
+ cy.get('[data-testid="yaxis-metric-selector"]').click({ force: true });
+ cy.contains('[role="option"]', 'Cost per Million Total Tokens (Owning - Hyperscaler)').click({
+ force: true,
+ });
cy.get('[data-testid="scatter-graph"]')
.first()
@@ -59,13 +58,13 @@ describe('URL Parameter Persistence', () => {
});
it('selecting a Y-axis metric updates the displayed value', () => {
- cy.visit('/');
- cy.get('[data-testid="yaxis-metric-selector"]').click();
+ cy.visit('/inference');
+ cy.get('[data-testid="yaxis-metric-selector"]').click({ force: true });
cy.get('[role="option"]')
.eq(1)
.then(($option) => {
const optionText = $option.text().trim();
- cy.wrap($option).click();
+ cy.wrap($option).click({ force: true });
cy.get('[data-testid="yaxis-metric-selector"]')
.invoke('text')
.should('include', optionText);
@@ -73,11 +72,13 @@ describe('URL Parameter Persistence', () => {
});
it('switching to energy metric updates SVG axis label to joules', () => {
- cy.visit('/');
+ cy.visit('/inference');
cy.get('[data-testid="scatter-graph"]').first().should('be.visible');
- cy.get('[data-testid="yaxis-metric-selector"]').click();
- cy.contains('[role="option"]', 'All-in Provisioned Joules per Total Token').click();
+ cy.get('[data-testid="yaxis-metric-selector"]').click({ force: true });
+ cy.contains('[role="option"]', 'All-in Provisioned Joules per Total Token').click({
+ force: true,
+ });
cy.get('[data-testid="scatter-graph"]')
.first()
@@ -86,7 +87,7 @@ describe('URL Parameter Persistence', () => {
});
it('i_metric=y_tpPerMw pre-selects throughput-per-MW', () => {
- cy.visit('/?i_metric=y_tpPerMw');
+ cy.visit('/inference?i_metric=y_tpPerMw');
cy.get('[data-testid="yaxis-metric-selector"]').should(
'contain.text',
@@ -103,21 +104,21 @@ describe('URL Parameter Persistence', () => {
describe('Reliability date range', () => {
it('r_range=last-7-days pre-selects date range', () => {
cy.visit('/reliability?r_range=last-7-days');
- cy.get('[data-testid="tab-trigger-reliability"]').should('have.attr', 'data-state', 'active');
+ cy.url().should('include', '/reliability');
cy.get('[data-testid="reliability-date-range"]').should('contain.text', 'Last 7 days');
});
it('r_range=last-3-months pre-selects "Last 3 months"', () => {
cy.visit('/reliability?r_range=last-3-months');
- cy.get('[data-testid="tab-trigger-reliability"]').should('have.attr', 'data-state', 'active');
+ cy.url().should('include', '/reliability');
cy.get('[data-testid="reliability-date-range"]').should('contain.text', 'Last 3 months');
});
it('changing reliability date range updates displayed selection', () => {
cy.visit('/reliability');
- cy.get('[data-testid="tab-trigger-reliability"]').should('have.attr', 'data-state', 'active');
- cy.get('[data-testid="reliability-date-range"]').click();
- cy.contains('[role="option"]', 'Last month').click();
+ cy.url().should('include', '/reliability');
+ cy.get('[data-testid="reliability-date-range"]').click({ force: true });
+ cy.contains('[role="option"]', 'Last month').click({ force: true });
cy.get('[data-testid="reliability-date-range"]').should('contain', 'Last month');
});
});
diff --git a/packages/app/cypress/e2e/yaxis-metrics-render.cy.ts b/packages/app/cypress/e2e/yaxis-metrics-render.cy.ts
index 8450f95..351061e 100644
--- a/packages/app/cypress/e2e/yaxis-metrics-render.cy.ts
+++ b/packages/app/cypress/e2e/yaxis-metrics-render.cy.ts
@@ -32,7 +32,7 @@ describe('Y-Axis Metrics All Render Data', () => {
cy.window().then((win) => {
win.localStorage.setItem('inferencex-star-modal-dismissed', String(Date.now()));
});
- cy.visit('/');
+ cy.visit('/inference');
cy.get('[data-testid="scatter-graph"]')
.first()
.find('svg .dot-group')
@@ -41,8 +41,8 @@ describe('Y-Axis Metrics All Render Data', () => {
metrics.forEach((label) => {
it(`"${label}" renders scatter points without extra interaction`, () => {
- cy.get('[data-testid="yaxis-metric-selector"]').click();
- cy.get('[role="option"]').contains(label).click();
+ cy.get('[data-testid="yaxis-metric-selector"]').click({ force: true });
+ cy.get('[role="option"]').contains(label).click({ force: true });
cy.get('[data-testid="scatter-graph"]')
.first()
.find('svg .dot-group')
diff --git a/packages/app/cypress/support/mock-data.ts b/packages/app/cypress/support/mock-data.ts
index eb0b251..fe60c74 100644
--- a/packages/app/cypress/support/mock-data.ts
+++ b/packages/app/cypress/support/mock-data.ts
@@ -34,42 +34,36 @@ export function createMockHardwareConfig(): HardwareConfig {
label: 'H100',
suffix: '',
gpu: "NVIDIA 'Hopper' H100",
- color: 'var(--h100)',
},
h200: {
name: 'h200',
label: 'H200',
suffix: '',
gpu: "NVIDIA 'Hopper' H200",
- color: 'var(--h200)',
},
b200: {
name: 'b200',
label: 'B200',
suffix: '',
gpu: "NVIDIA 'Blackwell' B200",
- color: 'var(--b200)',
},
b200_trt: {
name: 'b200-trt',
label: 'B200',
suffix: '(TRT)',
gpu: "NVIDIA 'Blackwell' B200 TRT",
- color: 'var(--b200-trt)',
},
mi300x: {
name: 'mi300x',
label: 'MI300X',
suffix: '',
gpu: 'AMD Instinct MI300X',
- color: 'var(--mi300x)',
},
h100_vllm: {
name: 'h100-vllm',
label: 'H100',
suffix: '(vLLM)',
gpu: "NVIDIA 'Hopper' H100 vLLM",
- color: 'var(--h100-vllm)',
framework: 'vllm',
},
b200_sglang: {
@@ -77,7 +71,6 @@ export function createMockHardwareConfig(): HardwareConfig {
label: 'B200',
suffix: '(SGLang)',
gpu: "NVIDIA 'Blackwell' B200 SGLang",
- color: 'var(--b200-sglang)',
framework: 'sglang',
},
};
@@ -170,8 +163,10 @@ export function createMockInferenceContext(
activeHwTypes: new Set(['h100', 'b200', 'b200_trt', 'mi300x', 'h200']),
hwTypesWithData: new Set(['h100', 'b200', 'b200_trt', 'mi300x', 'h200']),
toggleHwType: namedStub('toggleHwType'),
+ removeHwType: namedStub('removeHwType'),
selectAllHwTypes: namedStub('selectAllHwTypes'),
toggleActiveDate: namedStub('toggleActiveDate'),
+ removeActiveDate: namedStub('removeActiveDate'),
selectAllActiveDates: namedStub('selectAllActiveDates'),
activeDates: new Set(['2025-03-01']),
hardwareConfig: hwConfig,
@@ -310,6 +305,7 @@ export function createMockEvaluationContext(
unfilteredChartData: [createMockEvaluationChartData()],
enabledHardware: new Set(['b200_trt', 'h100', 'mi300x']),
toggleHardware: namedStub('toggleHardware'),
+ removeHardware: namedStub('removeHardware'),
highContrast: false,
setHighContrast: namedStub('setHighContrast_eval'),
showLabels: true,
@@ -321,6 +317,9 @@ export function createMockEvaluationContext(
highlightedConfigs: new Set(),
changelogEntries: [],
modelHasEvalData: true,
+ selectedPrecisions: ['fp4'],
+ setSelectedPrecisions: namedStub('setSelectedPrecisions_eval'),
+ availablePrecisions: ['fp4', 'fp8', 'bf16'],
...overrides,
};
}
@@ -373,6 +372,7 @@ export function createMockReliabilityContext(
setHighContrast: namedStub('setHighContrast_reliability'),
enabledModels: new Set([Model.DeepSeek_R1]),
toggleModel: namedStub('toggleModel'),
+ removeModel: namedStub('removeModel'),
isLegendExpanded: true,
setIsLegendExpanded: namedStub('setIsLegendExpanded_reliability'),
modelsWithData: new Set([Model.DeepSeek_R1]),
diff --git a/packages/app/package.json b/packages/app/package.json
index 9af8087..cc68028 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -2,7 +2,7 @@
"name": "@semianalysisai/inferencex-app",
"version": "0.1.0",
"private": true,
- "description": "InferenceX (formerly InferenceMAX) - Inference performance benchmarking and visualization",
+ "description": "InferenceX - Inference performance benchmarking and visualization",
"repository": {
"type": "git",
"url": "https://github.com/SemiAnalysisAI/inferencemax-app"
@@ -57,7 +57,8 @@
"react-dom": "19.2.4",
"remark-gfm": "^4.0.1",
"shiki": "^4.0.2",
- "tailwind-merge": "^3.5.0"
+ "tailwind-merge": "^3.5.0",
+ "zod": "^4.3.6"
},
"devDependencies": {
"@bahmutov/cypress-esbuild-preprocessor": "^2.2.8",
diff --git a/packages/app/scripts/warmup-cache.ts b/packages/app/scripts/warmup-cache.ts
index 7452b7f..69b2e44 100644
--- a/packages/app/scripts/warmup-cache.ts
+++ b/packages/app/scripts/warmup-cache.ts
@@ -51,7 +51,6 @@ async function warmupCaches() {
hit('/api/v1/availability'),
hit('/api/v1/reliability'),
hit('/api/v1/evaluations'),
- hit('/api/v1/github-stars'),
]);
// --- Benchmarks (latest) per model ---
diff --git a/packages/app/src/app/(dashboard)/calculator/page.tsx b/packages/app/src/app/(dashboard)/calculator/page.tsx
new file mode 100644
index 0000000..0bd2ead
--- /dev/null
+++ b/packages/app/src/app/(dashboard)/calculator/page.tsx
@@ -0,0 +1,10 @@
+import type { Metadata } from 'next';
+
+import ThroughputCalculatorDisplay from '@/components/calculator/ThroughputCalculatorDisplay';
+import { tabMetadata } from '@/lib/tab-meta';
+
+export const metadata: Metadata = tabMetadata('calculator');
+
+export default function CalculatorPage() {
+ return ;
+}
diff --git a/packages/app/src/app/(dashboard)/evaluation/page.tsx b/packages/app/src/app/(dashboard)/evaluation/page.tsx
new file mode 100644
index 0000000..f1be9b0
--- /dev/null
+++ b/packages/app/src/app/(dashboard)/evaluation/page.tsx
@@ -0,0 +1,15 @@
+import type { Metadata } from 'next';
+
+import { EvaluationProvider } from '@/components/evaluation/EvaluationContext';
+import EvaluationChartDisplay from '@/components/evaluation/ui/ChartDisplay';
+import { tabMetadata } from '@/lib/tab-meta';
+
+export const metadata: Metadata = tabMetadata('evaluation');
+
+export default function EvaluationPage() {
+ return (
+
+
+
+ );
+}
diff --git a/packages/app/src/app/(dashboard)/gpu-metrics/page.tsx b/packages/app/src/app/(dashboard)/gpu-metrics/page.tsx
new file mode 100644
index 0000000..8669582
--- /dev/null
+++ b/packages/app/src/app/(dashboard)/gpu-metrics/page.tsx
@@ -0,0 +1,10 @@
+import type { Metadata } from 'next';
+
+import GpuMetricsDisplay from '@/components/gpu-power/GpuPowerDisplay';
+import { tabMetadata } from '@/lib/tab-meta';
+
+export const metadata: Metadata = tabMetadata('gpu-metrics');
+
+export default function GpuMetricsPage() {
+ return ;
+}
diff --git a/packages/app/src/app/(dashboard)/gpu-specs/page.tsx b/packages/app/src/app/(dashboard)/gpu-specs/page.tsx
new file mode 100644
index 0000000..f93f719
--- /dev/null
+++ b/packages/app/src/app/(dashboard)/gpu-specs/page.tsx
@@ -0,0 +1,10 @@
+import type { Metadata } from 'next';
+
+import { GpuSpecsContent } from '@/components/gpu-specs/gpu-specs-content';
+import { tabMetadata } from '@/lib/tab-meta';
+
+export const metadata: Metadata = tabMetadata('gpu-specs');
+
+export default function GpuSpecsPage() {
+ return ;
+}
diff --git a/packages/app/src/app/(dashboard)/historical/page.tsx b/packages/app/src/app/(dashboard)/historical/page.tsx
new file mode 100644
index 0000000..e790a27
--- /dev/null
+++ b/packages/app/src/app/(dashboard)/historical/page.tsx
@@ -0,0 +1,15 @@
+import type { Metadata } from 'next';
+
+import { InferenceProvider } from '@/components/inference/InferenceContext';
+import HistoricalTrendsDisplay from '@/components/trends/HistoricalTrendsDisplay';
+import { tabMetadata } from '@/lib/tab-meta';
+
+export const metadata: Metadata = tabMetadata('historical');
+
+export default function HistoricalPage() {
+ return (
+
+
+
+ );
+}
diff --git a/packages/app/src/app/(dashboard)/inference/page.tsx b/packages/app/src/app/(dashboard)/inference/page.tsx
new file mode 100644
index 0000000..894ac0d
--- /dev/null
+++ b/packages/app/src/app/(dashboard)/inference/page.tsx
@@ -0,0 +1,15 @@
+import type { Metadata } from 'next';
+
+import { InferenceProvider } from '@/components/inference/InferenceContext';
+import InferenceChartDisplay from '@/components/inference/ui/ChartDisplay';
+import { tabMetadata } from '@/lib/tab-meta';
+
+export const metadata: Metadata = tabMetadata('inference');
+
+export default function InferencePage() {
+ return (
+
+
+
+ );
+}
diff --git a/packages/app/src/app/(dashboard)/layout.tsx b/packages/app/src/app/(dashboard)/layout.tsx
new file mode 100644
index 0000000..4d38318
--- /dev/null
+++ b/packages/app/src/app/(dashboard)/layout.tsx
@@ -0,0 +1,5 @@
+import { DashboardShell } from '@/components/dashboard-shell';
+
+export default function DashboardLayout({ children }: { children: React.ReactNode }) {
+ return {children};
+}
diff --git a/packages/app/src/app/(dashboard)/reliability/page.tsx b/packages/app/src/app/(dashboard)/reliability/page.tsx
new file mode 100644
index 0000000..6c647d3
--- /dev/null
+++ b/packages/app/src/app/(dashboard)/reliability/page.tsx
@@ -0,0 +1,15 @@
+import type { Metadata } from 'next';
+
+import { ReliabilityProvider } from '@/components/reliability/ReliabilityContext';
+import ReliabilityChartDisplay from '@/components/reliability/ui/ChartDisplay';
+import { tabMetadata } from '@/lib/tab-meta';
+
+export const metadata: Metadata = tabMetadata('reliability');
+
+export default function ReliabilityPage() {
+ return (
+
+
+
+ );
+}
diff --git a/packages/app/src/app/(landing)/page.tsx b/packages/app/src/app/(landing)/page.tsx
new file mode 100644
index 0000000..64097c9
--- /dev/null
+++ b/packages/app/src/app/(landing)/page.tsx
@@ -0,0 +1,24 @@
+import type { Metadata } from 'next';
+
+import { LandingPage } from '@/components/landing/landing-page';
+import { LANDING_META } from '@/lib/tab-meta';
+import { SITE_URL } from '@semianalysisai/inferencex-constants';
+
+export const metadata: Metadata = {
+ title: LANDING_META.title,
+ description: LANDING_META.description,
+ alternates: { canonical: SITE_URL },
+ openGraph: {
+ title: `${LANDING_META.title} | InferenceX`,
+ description: LANDING_META.description,
+ url: SITE_URL,
+ },
+ twitter: {
+ title: `${LANDING_META.title} | InferenceX`,
+ description: LANDING_META.description,
+ },
+};
+
+export default function HomePage() {
+ return ;
+}
diff --git a/packages/app/src/app/[[...tab]]/page.tsx b/packages/app/src/app/[[...tab]]/page.tsx
deleted file mode 100644
index acd78bf..0000000
--- a/packages/app/src/app/[[...tab]]/page.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { Metadata } from 'next';
-import { notFound } from 'next/navigation';
-
-import { PageContent } from '@/components/page-content';
-import { TAB_META, VALID_TABS } from '@/lib/tab-meta';
-import { SITE_URL } from '@semianalysisai/inferencex-constants';
-
-export const dynamicParams = true;
-
-export function generateStaticParams() {
- return [{ tab: [] }, ...VALID_TABS.map((t) => ({ tab: [t] }))];
-}
-
-export async function generateMetadata({
- params,
-}: {
- params: Promise<{ tab?: string[] }>;
-}): Promise {
- const { tab } = await params;
- const activeTab = tab?.[0] ?? 'inference';
- const meta = TAB_META[activeTab as keyof typeof TAB_META];
- if (!meta) return {};
-
- const url = activeTab === 'inference' ? SITE_URL : `${SITE_URL}/${activeTab}`;
-
- return {
- title: meta.title,
- description: meta.description,
- alternates: { canonical: url },
- openGraph: {
- title: `${meta.title} | InferenceX`,
- description: meta.description,
- url,
- },
- twitter: {
- title: `${meta.title} | InferenceX`,
- description: meta.description,
- },
- };
-}
-
-export default async function Page({ params }: { params: Promise<{ tab?: string[] }> }) {
- const { tab } = await params;
- const activeTab = tab?.[0] ?? 'inference';
-
- if (!VALID_TABS.includes(activeTab as (typeof VALID_TABS)[number]) || (tab && tab.length > 1)) {
- notFound();
- }
-
- return ;
-}
diff --git a/packages/app/src/app/about/page.tsx b/packages/app/src/app/about/page.tsx
new file mode 100644
index 0000000..776e455
--- /dev/null
+++ b/packages/app/src/app/about/page.tsx
@@ -0,0 +1,126 @@
+import type { Metadata } from 'next';
+import Link from 'next/link';
+
+import { Card } from '@/components/ui/card';
+import { FAQ_ITEMS } from '@/components/about/faq-data';
+import { SITE_URL } from '@semianalysisai/inferencex-constants';
+
+const faqJsonLd = {
+ '@context': 'https://schema.org',
+ '@type': 'FAQPage',
+ mainEntity: FAQ_ITEMS.map((item) => ({
+ '@type': 'Question',
+ name: item.question,
+ acceptedAnswer: {
+ '@type': 'Answer',
+ text: [item.answer, item.link?.text, ...(item.list ?? [])].filter(Boolean).join(' '),
+ },
+ })),
+};
+
+export const metadata: Metadata = {
+ title: 'About',
+ description:
+ 'InferenceX is an independent, vendor neutral, reproducible benchmark which continuously benchmarks inference software across a wide range of AI accelerators.',
+ alternates: { canonical: `${SITE_URL}/about` },
+ openGraph: {
+ title: 'About | InferenceX',
+ description:
+ 'InferenceX is an independent, vendor neutral, reproducible benchmark which continuously benchmarks inference software across a wide range of AI accelerators.',
+ url: `${SITE_URL}/about`,
+ },
+ twitter: {
+ title: 'About | InferenceX',
+ description:
+ 'InferenceX is an independent, vendor neutral, reproducible benchmark which continuously benchmarks inference software across a wide range of AI accelerators.',
+ },
+};
+
+export default function AboutPage() {
+ return (
+
+
+
+
+
+
+ Open Source Continuous Inference Benchmark trusted by Operators of Trillion Dollar
+ GigaWatt Scale Token Factories
+
+
+ As the world progresses exponentially towards AGI, software development and model
+ releases move at the speed of light. Existing benchmarks rapidly become obsolete due
+ to their static nature, and participants often submit software images purpose-built
+ for the benchmark itself which do not reflect real world performance.
+
+
+ InferenceX™ (formerly InferenceMAX) is our independent, vendor
+ neutral, reproducible benchmark which addresses these issues by continuously
+ benchmarking inference software across a wide range of AI accelerators that are
+ actually available to the ML community.
+
+
+ Our open data & insights are widely adopted by the ML community, capacity planning
+ strategy teams at trillion dollar token factories & AI Labs & at multiple billion
+ dollar NeoClouds. Learn more in our articles:{' '}
+
+ InferenceX v1
+
+ ,{' '}
+
+ InferenceX v2
+
+ .
+
-
+
-
-
+ {process.env.VERCEL && }
+ {process.env.VERCEL && }
diff --git a/packages/app/src/app/quotes/page.tsx b/packages/app/src/app/quotes/page.tsx
index f1c69fe..8f9bc25 100644
--- a/packages/app/src/app/quotes/page.tsx
+++ b/packages/app/src/app/quotes/page.tsx
@@ -6,7 +6,7 @@ import { SITE_URL } from '@semianalysisai/inferencex-constants';
export const metadata: Metadata = {
title: 'Supporters',
description:
- 'InferenceX (formerly InferenceMAX) initiative is supported by major buyers of compute and prominent members of the ML community including those from OpenAI, Microsoft, PyTorch Foundation, and more.',
+ 'InferenceX initiative is supported by major buyers of compute and prominent members of the ML community including those from OpenAI, Microsoft, PyTorch Foundation, and more.',
alternates: { canonical: `${SITE_URL}/quotes` },
openGraph: {
title: 'Supporters | InferenceX by SemiAnalysis',
diff --git a/packages/app/src/components/about/faq-data.ts b/packages/app/src/components/about/faq-data.ts
new file mode 100644
index 0000000..97d22e2
--- /dev/null
+++ b/packages/app/src/components/about/faq-data.ts
@@ -0,0 +1,114 @@
+import {
+ GPU_KEYS,
+ GPU_VENDORS,
+ DB_MODEL_TO_DISPLAY,
+ PRECISION_KEYS,
+ GITHUB_OWNER,
+ GITHUB_REPO,
+} from '@semianalysisai/inferencex-constants';
+import { FRAMEWORK_LABELS } from '@/lib/constants';
+import { CAROUSEL_ORGS, CAROUSEL_LABELS } from '@/components/quotes/quotes-data';
+
+export interface FaqLink {
+ text: string;
+ href: string;
+}
+
+export interface FaqItem {
+ question: string;
+ /** Intro text shown before any list. */
+ answer: string;
+ /** Optional link rendered inline after the answer text. */
+ link?: FaqLink;
+ /** Optional bullet list rendered below the answer text. */
+ list?: string[];
+}
+
+/* ---------- Dynamic lists from constants ---------- */
+
+const gpusByVendor = [...GPU_KEYS].reduce>((acc, key) => {
+ const vendor = GPU_VENDORS[key] ?? 'Other';
+ (acc[vendor] ??= []).push(key.toUpperCase());
+ return acc;
+}, {});
+const modelNames = Object.values(DB_MODEL_TO_DISPLAY);
+
+const frameworkNames = [...new Set(Object.values(FRAMEWORK_LABELS))].map((n) =>
+ n.replace(/[¹²³⁴⁵⁶⁷⁸⁹⁰]+$/, ''),
+);
+
+const supporterOrgs = CAROUSEL_ORGS.map((org) => CAROUSEL_LABELS[org] ?? org);
+
+/* ---------- FAQ content ---------- */
+
+export const FAQ_ITEMS: FaqItem[] = [
+ {
+ question: 'What is InferenceX?',
+ answer:
+ 'InferenceX (formerly InferenceMAX) is an open-source, vendor-neutral benchmark that continuously measures AI inference performance across GPUs and software stacks. Benchmarks re-run whenever a configuration changes, so results stay current as models and frameworks evolve.',
+ },
+ {
+ question: 'Who is behind InferenceX?',
+ answer: `InferenceX is built by SemiAnalysis, an independent semiconductor and AI research firm. It is supported and trusted by ${supporterOrgs.join(', ')}. The benchmark code, data, and dashboard are all open-source on GitHub.`,
+ },
+ {
+ question: 'Which GPUs does InferenceX benchmark?',
+ answer: 'New accelerators are added as they become available.',
+ list: Object.entries(gpusByVendor).map(([vendor, gpus]) => `${vendor}: ${gpus.join(', ')}`),
+ },
+ {
+ question: 'Which AI models are tested?',
+ answer:
+ 'Each model is tested across multiple sequence length configurations (1k/1k, 1k/8k, 8k/1k tokens) and concurrency levels.',
+ list: modelNames,
+ },
+ {
+ question: 'Which inference frameworks and configurations are tested?',
+ answer: '',
+ list: [
+ `Frameworks: ${frameworkNames.join(', ')}`,
+ `Precisions: ${[...PRECISION_KEYS].map((p) => p.toUpperCase()).join(', ')}`,
+ 'Runtimes: CUDA, ROCm',
+ 'Disaggregated serving (separate prefill/decode GPU pools)',
+ 'Multi-token prediction (MTP)',
+ 'Wide expert parallelism for MoE models',
+ ],
+ },
+ {
+ question: 'What metrics does InferenceX measure?',
+ answer: '',
+ list: [
+ 'Interactivity (tok/s/user)',
+ 'Token throughput per GPU (tok/s/gpu)',
+ 'Input and output throughput per GPU',
+ 'Token throughput per MW (tok/s/MW)',
+ 'P99 time to first token (TTFT)',
+ 'Cost per million tokens (total, input, output) across hyperscaler, neocloud, and rental pricing',
+ 'Joules per token (total, input, output)',
+ 'Custom user-defined cost and power calculations',
+ ],
+ },
+ {
+ question: 'How often are benchmarks run?',
+ answer:
+ 'Benchmarks originally ran on a nightly schedule, but the number of hardware/framework/model combinations grew too large for that to be practical. Now they re-run when a configuration changes, e.g. a new software release, driver update, or model addition. Historical data is available in the dashboard.',
+ },
+ {
+ question: 'Is InferenceX open source?',
+ answer: 'Yes. Code, data, and dashboard are all open-source.',
+ link: {
+ text: `${GITHUB_OWNER}/${GITHUB_REPO}`,
+ href: `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}`,
+ },
+ },
+ {
+ question: 'How is InferenceX different from other AI benchmarks?',
+ answer:
+ 'Most AI benchmarks are static, point-in-time measurements where participants submit purpose-built images that do not reflect real-world serving performance. InferenceX runs continuously on real hardware with fully reproducible configurations. Every recipe is in the repo, benchmark logs are visible on GitHub Actions, and all results are auditable end-to-end.',
+ },
+ {
+ question: 'Can I use InferenceX data for my own analysis?',
+ answer:
+ 'Yes. All data is freely available. The dashboard lets you filter by GPU, model, framework, and date range, and you can export raw CSV data directly from any chart.',
+ },
+];
diff --git a/packages/app/src/components/blog/blog-post-card.tsx b/packages/app/src/components/blog/blog-post-card.tsx
index 686d7bb..397eab9 100644
--- a/packages/app/src/components/blog/blog-post-card.tsx
+++ b/packages/app/src/components/blog/blog-post-card.tsx
@@ -1,6 +1,7 @@
'use client';
import Link from 'next/link';
+import { ArrowRight } from 'lucide-react';
import type { ReactNode } from 'react';
import { track } from '@/lib/analytics';
@@ -14,10 +15,14 @@ export function BlogPostCard({ slug, title, children }: BlogPostCardProps) {
return (
track('blog_post_clicked', { slug, title })}
>
- {children}
+
+