Skip to content

Commit 8c53dd6

Browse files
feat(embed): make url params consistent with main site, add i_active allow list for gpus
1 parent 3be4ccc commit 8c53dd6

7 files changed

Lines changed: 309 additions & 457 deletions

File tree

docs/embed.md

Lines changed: 26 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
# Embed Routes
22

3-
Public-contract, stable iframe-embedding surface for InferenceX charts. Partner sites can embed any supported chart by iframing an `/embed/*` URL and rely on it continuing to work long-term.
3+
Public iframe-embedding surface for InferenceX charts. Partner sites can embed any supported chart by iframing an `/embed/*` URL.
44

5-
## Stability guarantee
5+
## URL parameter contract
66

7-
The embed URL shape is a **public API**. The following rules apply permanently:
8-
9-
- Existing parameter keys **cannot be removed or renamed**. New keys may be added with sensible defaults.
10-
- Existing `y` short-form aliases **cannot be removed or renamed**. New aliases may be added.
11-
- Existing `gpus` hwKey tokens **cannot be removed or renamed**. New tokens may be added when new hardware is added to the benchmark.
12-
- Unknown values always fall back to the documented default — a stale embed URL must never render an error page.
13-
- If a rename becomes unavoidable, the old key/alias must be kept as a permanently-handled alias alongside the new one, and a note added to this file.
7+
Embed URLs use **the same `g_*` / `i_*` parameter keys as the main `/inference` site** — there is no separate embed-specific key contract to maintain. If a site key is renamed or a new key is added, the embed URL automatically benefits from the change. The only embed-specific key is `i_chart` (which chart variant to display — the main site renders both E2E and interactivity together, embeds show only one).
148

159
## Supported routes
1610

@@ -23,74 +17,37 @@ The embed URL shape is a **public API**. The following rules apply permanently:
2317
### URL shape
2418

2519
```
26-
/embed/scatter?model=dsr1&isl=8192&osl=1024&precisions=fp4&gpus=b200_sglang,gb300_dynamo-sglang&y=tpPerGpu&chart=e2e
20+
/embed/scatter?g_model=DeepSeek-R1-0528&i_seq=8k%2F1k&i_prec=fp4
21+
&i_metric=y_tpPerGpu&i_active=b200_sglang,gb300_dynamo-sglang&i_chart=e2e
2722
```
2823

2924
### Parameters
3025

31-
| Key | Type | Default | Accepted values | Fallback on unknown |
32-
| ------------ | -------------- | ---------------- | ----------------------------------------------------------------------- | ---------------------- |
33-
| `model` | string | `dsr1` | Any DB model key (see table below) | Default model (`dsr1`) |
34-
| `isl` | integer string | `8192` | Input sequence length in tokens | Default (`8192`) |
35-
| `osl` | integer string | `1024` | Output sequence length in tokens | Default (`1024`) |
36-
| `precisions` | string | `fp4` | Comma-separated precision keys (e.g. `fp4`, `fp8`, `bf16`) | Default (`fp4`) |
37-
| `gpus` | string | `` (all visible) | Comma-separated hwKey tokens (see format below) | Empty — all GPUs shown |
38-
| `y` | string | `tpPerGpu` | Any short-form alias from the table below, or a full `y_*` internal key | Default (`tpPerGpu`) |
39-
| `chart` | string | `e2e` | `e2e`, `interactivity` | `e2e` |
40-
41-
### `model` — accepted DB keys
42-
43-
These are the short DB keys accepted as `model=`. They map to the display model names shown in the chart. Unknown keys fall back to the default (`dsr1`).
44-
45-
The authoritative list lives in `DB_MODEL_TO_DISPLAY` in `packages/constants/src/`. Representative examples:
46-
47-
| `model=` value | Display name |
48-
| -------------- | -------------------------- |
49-
| `dsr1` | DeepSeek-R1-0528 |
50-
| `dsv4` | DeepSeek-V4-Pro |
51-
| `llama70b` | Llama-3.3-70B-Instruct-FP8 |
52-
| `llama405b` | Llama-3.1-405B-Instruct |
53-
| `qwen72b` | Qwen2.5-72B-Instruct |
54-
55-
### `y` — accepted short-form aliases
56-
57-
Both the short form (`tpPerGpu`) and the full internal form (`y_tpPerGpu`) are accepted. **Short-form aliases listed here cannot be removed.**
58-
59-
| `y=` value | Meaning |
60-
| ------------------ | ----------------------------------------------------- |
61-
| `tpPerGpu` | Total throughput per GPU (tokens/s/GPU) — **default** |
62-
| `inputTputPerGpu` | Input throughput per GPU |
63-
| `outputTputPerGpu` | Output throughput per GPU |
64-
| `tpPerMw` | Total throughput per MW |
65-
| `inputTputPerMw` | Input throughput per MW |
66-
| `outputTputPerMw` | Output throughput per MW |
67-
| `costh` | Cost per hour |
68-
| `costn` | Cost per 1 M input tokens |
69-
| `costr` | Cost per 1 M output tokens |
70-
| `costhOutput` | Cost per hour (output) |
71-
| `costnOutput` | Cost per 1 M input tokens (output) |
72-
| `costrOutput` | Cost per 1 M output tokens (output) |
73-
| `costhi` | Cost per hour (interactivity) |
74-
| `costni` | Cost per 1 M input tokens (interactivity) |
75-
| `costri` | Cost per 1 M output tokens (interactivity) |
76-
| `jTotal` | Joules per total token |
77-
| `jOutput` | Joules per output token |
78-
| `jInput` | Joules per input token |
79-
80-
### `gpus` — hwKey format
81-
82-
The `gpus` parameter is a comma-separated list of hwKey tokens. Each token encodes the hardware and the inference framework together, separated by an underscore (e.g. `b200_sglang`, `gb300_dynamo-sglang`).
83-
84-
**These tokens are stable.** Any hwKey token that has ever been publicly supported in an embed URL must continue to be accepted. New tokens are added when new hardware or framework combinations are added to the benchmark. Existing tokens cannot be removed; retired hardware tokens simply filter to no visible points (which is the same as the user having toggled that GPU off).
85-
86-
To discover the current set of valid hwKeys, visit `/inference` on the live site, open the legend, and note the identifiers shown — or use **Export → Copy embed** on the chart to copy an iframe snippet that already encodes your current filters.
26+
| Key | Type | Default | Notes |
27+
| ---------- | ------ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
28+
| `g_model` | string | `DeepSeek-R1-0528` | Display model name — same as `g_model` on the main site. |
29+
| `i_seq` | string | `8k/1k` | Sequence string (e.g. `8k/1k`, `1k/1k`, `1k/8k`) — same as `i_seq` on the main site. |
30+
| `i_prec` | string | `fp4` | Comma-separated precision keys (e.g. `fp4`, `fp8`, `bf16`) — same as `i_prec` on the main site. |
31+
| `i_metric` | string | `y_tpPerGpu` | Y-axis metric key (e.g. `y_tpPerGpu`, `y_costh`) — same as `i_metric` on the main site. |
32+
| `i_active` | string | `` (all visible) | Comma-separated hwKey allow-list (e.g. `b200_sglang,gb300_dynamo-sglang`). When set, the embed legend and chart universe are restricted to exactly these GPUs. Viewers can toggle them on/off but cannot add GPUs outside this set. When absent, all GPUs for the selected model/sequence/precision are shown. |
33+
| `i_chart` | string | `e2e` | Chart variant to render: `e2e` or `interactivity`. Embed-only key — the main site renders both charts together. |
34+
35+
All other `g_*` / `i_*` keys recognized by the main site (e.g. `i_scale`, `i_hc`, `i_nolabel`) are passed through as-is and respected by the embed — the provider stack is identical. Unknown keys are silently ignored.
36+
37+
### `i_active` — hwKey format
38+
39+
Each hwKey token encodes hardware and inference framework together, separated by an underscore (e.g. `b200_sglang`, `gb300_dynamo-sglang`). To find valid hwKey values, visit `/inference` on the live site, open the legend, and note the identifiers shown — or use **Export → Copy embed** to get a ready-made URL with your current filters already encoded.
40+
41+
### `i_metric` — accepted values
42+
43+
Full `y_*` internal keys (e.g. `y_tpPerGpu`, `y_costh`). The authoritative list is in `packages/app/src/lib/chart-utils.ts` (`Y_AXIS_METRICS`).
8744

8845
## Embed mode behavior
8946

9047
- Site header, footer, background decorations, and navigation are hidden on all `/embed/*` routes.
91-
- A "SemiAnalysis InferenceX →" link appears in the chart caption (`Source: …`), deep-linking to the equivalent canonical dashboard URL.
48+
- A "SemiAnalysis InferenceX →" link appears in the chart caption (`Source: …`), deep-linking to the equivalent canonical dashboard URL. The canonical URL is built from the same embed params (minus `i_chart`), so opening it reproduces the same chart state on the main site.
9249
- `robots: noindex, nofollow` is set on all embed routes — they won't appear in search results.
93-
- An `embed_view` PostHog event is fired once on mount, capturing `referrer`, `embed_host`, `embed_chart`, `model`, `sequence`, `gpus`, `y_metric`, and `precisions`. This makes external embed traffic attributable in analytics.
50+
- An `embed_view` PostHog event is fired once on mount, capturing `referrer`, `embed_host`, `embed_chart`, `model` (`g_model`), `sequence` (`i_seq`), `precisions` (`i_prec`), `gpus` (from `i_active`), and `y_metric` (`i_metric`). This makes external embed traffic attributable in analytics.
9451

9552
## CSP / framing
9653

@@ -102,7 +59,7 @@ All other routes set `frame-ancestors 'self'` and `X-Frame-Options: SAMEORIGIN`,
10259

10360
```html
10461
<iframe
105-
src="https://inferencex.semianalysis.com/embed/scatter?model=dsr1&isl=8192&osl=1024&precisions=fp4&y=tpPerGpu"
62+
src="https://inferencex.semianalysis.com/embed/scatter?g_model=DeepSeek-R1-0528&i_seq=8k%2F1k&i_prec=fp4&i_metric=y_tpPerGpu"
10663
width="800"
10764
height="500"
10865
loading="lazy"

packages/app/cypress/e2e/embed-scatter.cy.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ describe('Embed — Scatter Chart', () => {
5050
});
5151
});
5252

53-
describe('custom params', () => {
53+
describe('custom params (site-style keys)', () => {
5454
before(() => {
55-
cy.visit('/embed/scatter?model=dsr1&isl=8192&osl=1024&precisions=fp4&y=costh');
55+
cy.visit('/embed/scatter?g_model=DeepSeek-R1-0528&i_seq=8k%2F1k&i_prec=fp4&i_metric=y_costh');
5656
});
5757

5858
it('renders chart with the custom y metric', () => {
@@ -65,18 +65,49 @@ describe('Embed — Scatter Chart', () => {
6565
.should('have.attr', 'href')
6666
.and('include', 'i_metric=y_costh');
6767
});
68+
69+
it('canonical link does not include i_chart', () => {
70+
cy.get('[data-testid="embed-attribution"]')
71+
.should('have.attr', 'href')
72+
.and('not.include', 'i_chart');
73+
});
6874
});
6975

70-
describe('gpus param filters the legend', () => {
76+
describe('i_active param restricts legend to creator-selected GPUs', () => {
7177
before(() => {
72-
cy.visit('/embed/scatter?gpus=b200_sglang');
78+
cy.visit('/embed/scatter?i_active=b200_sglang');
7379
});
7480

75-
it('renders chart and legend reflects the active GPU set', () => {
81+
it('renders chart without "No data available"', () => {
7682
cy.get('[data-testid="embed-scatter-figure"]', { timeout: 15000 }).should('exist');
7783
cy.get('[data-testid="embed-scatter-figure"]').find('svg').should('exist');
7884
cy.contains('No data available').should('not.exist');
7985
});
86+
87+
it('legend contains only the allowed GPU', () => {
88+
// Each GPU renders as a <li> inside [data-testid="chart-legend"].
89+
// With i_active=b200_sglang, only that GPU is in the allow-list, so
90+
// exactly 1 <li> should appear (fp-indicators and controls use <div>).
91+
cy.get('[data-testid="embed-scatter-figure"]', { timeout: 15000 }).should('exist');
92+
cy.contains('No data available').should('not.exist');
93+
cy.get('[data-testid="embed-legend-panel"] [data-testid="chart-legend"] li').should(
94+
'have.length',
95+
1,
96+
);
97+
});
98+
});
99+
100+
describe('i_chart param selects chart variant', () => {
101+
it('renders e2e chart by default', () => {
102+
cy.visit('/embed/scatter');
103+
cy.get('[data-testid="embed-scatter-figure"]', { timeout: 15000 }).should('exist');
104+
});
105+
106+
it('renders interactivity chart when i_chart=interactivity', () => {
107+
cy.visit('/embed/scatter?i_chart=interactivity');
108+
cy.get('[data-testid="embed-scatter-figure"]', { timeout: 15000 }).should('exist');
109+
cy.contains('No data available').should('not.exist');
110+
});
80111
});
81112

82113
describe('CSP headers', () => {

packages/app/src/app/embed/scatter/embed-scatter-client.tsx

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import { GlobalFilterProvider } from '@/components/GlobalFilterContext';
77
import { InferenceProvider } from '@/components/inference/InferenceContext';
88
import { UnofficialRunProvider } from '@/components/unofficial-run-provider';
99
import { track } from '@/lib/analytics';
10-
import { type EmbedParams, embedParamsToUrlState } from '@/lib/embed-params';
11-
import { seedUrlState } from '@/lib/url-state';
10+
import { readEmbedChartVariant } from '@/lib/embed-params';
11+
import { type UrlStateParams, seedUrlState } from '@/lib/url-state';
1212

1313
interface Props {
14-
params: EmbedParams;
14+
params: UrlStateParams & { i_chart?: string };
1515
canonicalHref: string;
1616
}
1717

@@ -21,17 +21,35 @@ interface Props {
2121
* already reflects the requested embed params, then wraps the providers and
2222
* the chart display.
2323
*
24+
* Embed URLs use the same `g_*` / `i_*` keys as the main site — params flow
25+
* straight through `seedUrlState` with no translation. The only embed-specific
26+
* key is `i_chart` (which chart variant to render).
27+
*
28+
* When `i_active` is present, those hwKeys become the allow-list for the embed:
29+
* the legend and chart universe are restricted to exactly those GPUs. The
30+
* viewer can toggle them on/off but cannot add GPUs outside the set.
31+
*
2432
* Lives outside the `(dashboard)` route group, so we re-establish the
2533
* provider stack here (`UnofficialRunProvider` → `GlobalFilterProvider` →
2634
* `InferenceProvider`). `QueryProvider` is in the root layout and inherits.
2735
*/
2836
export default function EmbedScatterClient({ params, canonicalHref }: Props) {
2937
const seededRef = useRef(false);
3038
if (!seededRef.current) {
31-
seedUrlState(embedParamsToUrlState(params));
39+
// params are already in site-style UrlStateParams shape — seed directly.
40+
const { i_chart: _chart, ...urlParams } = params;
41+
seedUrlState(urlParams);
3242
seededRef.current = true;
3343
}
3444

45+
const chartType = readEmbedChartVariant(params.i_chart);
46+
47+
// Build the allow-list from i_active: restrict the embed legend/chart to
48+
// only the GPUs the creator chose. null means "no restriction" (all GPUs).
49+
const embedAllowedHwTypes = params.i_active
50+
? new Set(params.i_active.split(',').filter(Boolean))
51+
: null;
52+
3553
// Fire `embed_view` once on mount with referrer + host so external embed
3654
// traffic is attributable. Strict mode in dev double-fires effects, but
3755
// that's only in dev — production fires once.
@@ -46,16 +64,16 @@ export default function EmbedScatterClient({ params, canonicalHref }: Props) {
4664
} catch {
4765
embedHost = '';
4866
}
49-
const gpus = params.gpus ? params.gpus.split(',').filter(Boolean) : [];
67+
const gpus = params.i_active ? params.i_active.split(',').filter(Boolean) : [];
5068
track('embed_view', {
5169
embed_chart: 'scatter',
52-
chart_type: params.chart,
53-
model: params.model,
54-
sequence: `${params.isl}/${params.osl}`,
55-
precisions: params.precisions,
70+
chart_type: chartType,
71+
model: params.g_model,
72+
sequence: params.i_seq,
73+
precisions: params.i_prec,
5674
gpus,
5775
gpu_count: gpus.length,
58-
y_metric: params.y,
76+
y_metric: params.i_metric,
5977
referrer,
6078
embed_host: embedHost,
6179
});
@@ -64,10 +82,10 @@ export default function EmbedScatterClient({ params, canonicalHref }: Props) {
6482
return (
6583
<UnofficialRunProvider>
6684
<GlobalFilterProvider>
67-
<InferenceProvider activeTab="inference">
85+
<InferenceProvider activeTab="inference" embedAllowedHwTypes={embedAllowedHwTypes}>
6886
<div className="flex h-screen min-h-0 flex-col p-1 sm:p-2">
6987
<div className="min-h-0 flex-1">
70-
<EmbedScatterDisplay chartType={params.chart} canonicalHref={canonicalHref} />
88+
<EmbedScatterDisplay chartType={chartType} canonicalHref={canonicalHref} />
7189
</div>
7290
</div>
7391
</InferenceProvider>

packages/app/src/app/embed/scatter/page.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { Metadata } from 'next';
22

33
import { SITE_URL } from '@semianalysisai/inferencex-constants';
4-
import { buildCanonicalHref, readEmbedParams } from '@/lib/embed-params';
4+
import { buildCanonicalHref } from '@/lib/embed-params';
5+
import type { UrlStateParams } from '@/lib/url-state';
56

67
import EmbedScatterClient from './embed-scatter-client';
78

@@ -16,11 +17,13 @@ export default async function EmbedScatterPage({
1617
searchParams: Promise<Record<string, string | string[] | undefined>>;
1718
}) {
1819
const sp = await searchParams;
19-
const flat: Record<string, string | undefined> = {};
20+
const flat: UrlStateParams & { i_chart?: string } = {};
2021
for (const [k, v] of Object.entries(sp)) {
21-
flat[k] = Array.isArray(v) ? v[0] : v;
22+
const val = Array.isArray(v) ? v[0] : v;
23+
if (val !== undefined) {
24+
(flat as Record<string, string>)[k] = val;
25+
}
2226
}
23-
const params = readEmbedParams(flat);
24-
const canonicalHref = buildCanonicalHref(params, SITE_URL);
25-
return <EmbedScatterClient params={params} canonicalHref={canonicalHref} />;
27+
const canonicalHref = buildCanonicalHref(flat, SITE_URL);
28+
return <EmbedScatterClient params={flat} canonicalHref={canonicalHref} />;
2629
}

0 commit comments

Comments
 (0)