Skip to content
Merged
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
55 changes: 55 additions & 0 deletions packages/app/cypress/e2e/collectivex.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ const incompleteDataset = buildDataset({
conclusion: 'failure',
},
});
const kvDataset = buildDataset({
shards: [makeRawShard()],
kv: [
{},
{
sku: 'mi355x',
backend: 'mori-io',
fabric: 'rdma',
vendor: 'amd',
status: 'invalid',
reasons: ['transfer verification failed'],
},
],
meta: { run_id: '162', generated_at: '2026-08-07T12:20:00Z', source_sha: 'e'.repeat(40) },
});
const ADMIN_TOKEN_KEY = 'collectivex-admin-token';

function installRuns(bodies: CollectiveXDataset[] = [dataset]) {
Expand Down Expand Up @@ -477,3 +492,43 @@ describe('CollectiveX availability states', () => {
cy.then(() => expect(availabilityRequests).to.eq(0));
});
});

describe('CollectiveX kv-transfer card', () => {
it('renders kv cases with bandwidth-bound cells and per-case outcomes', () => {
installRuns([kvDataset]);
installRun(kvDataset);
openCollectiveX();
cy.get('[data-testid="collectivex-kv-table"]')
.should('be.visible')
.and('contain.text', 'KV-cache transfer')
.and('contain.text', '2 cases')
.and('contain.text', '1 measured');
cy.get('[data-testid="collectivex-kv-table-table"]').within(() => {
// The measured gb200 nixl case: bulk ceiling, paged-64 at batch 1 and
// at the largest measured batch, paged-16, and the handoff latency.
cy.contains('td', 'GB200').parent().as('measured');
cy.get('@measured').should('contain.text', 'nixl').and('contain.text', 'kv-dsv4');
cy.get('@measured').should('contain.text', '89.41');
cy.get('@measured').should('contain.text', '7.39');
cy.get('@measured').should('contain.text', '15.12 (b16)');
cy.get('@measured').should('contain.text', '2.72');
cy.get('@measured').should('contain.text', '24.8');
// The failed mori-io case keeps its outcome and reason, with no cells.
cy.contains('td', 'MI355X').parent().as('failed');
cy.get('@failed').should('contain.text', 'mori-io').and('contain.text', 'invalid');
cy.get('@failed').should('contain.text', 'transfer-verification-failed');
});
// KV cases count into the header stats alongside EP cases.
cy.get('[data-testid="collectivex-display"]').should(
'contain.text',
`${kvDataset.run.measured_cases}/${kvDataset.run.requested_cases}`,
);
});

it('renders no kv card for an EP-only run', () => {
installRuns();
installRun();
openCollectiveX();
cy.get('[data-testid="collectivex-kv-table"]').should('not.exist');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { useLocale } from '@/lib/use-locale';

import { CollectiveXChart } from './CollectiveXChart';
import { CollectiveXInventory } from './CollectiveXInventory';
import { CollectiveXKvTable } from './CollectiveXKvTable';
import { CollectiveXRunsTable } from './CollectiveXRunsTable';
import {
collectiveXColorKey,
Expand Down Expand Up @@ -1053,6 +1054,7 @@ export default function CollectiveXDisplay() {
/>
</div>
</Card>
<CollectiveXKvTable datasets={datasets} />
<CollectiveXInventory
key={`${version}-${datasets.map((dataset) => `${dataset.run.run_id}:${dataset.run.run_attempt}`).join(',')}`}
datasets={datasets}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,10 @@ export function CollectiveXInventory({ datasets }: { datasets: CollectiveXDatase
const points = rows.flatMap((item) => item.points);
const measured = points.filter((point) => point.terminal_status === 'measured').length;
const unsupported = points.filter((point) => point.terminal_status === 'unsupported').length;
const measuredCases = datasets.reduce((sum, dataset) => sum + dataset.run.measured_cases, 0);
const unsupportedCases = datasets.reduce(
(sum, dataset) => sum + dataset.run.unsupported_cases,
0,
);
// Counted from this table's own EP coverage rows: the run-level totals also
// include kv-transfer cases, which live in their own card, not here.
const measuredCases = rows.filter((row) => row.outcome === 'success').length;
const unsupportedCases = rows.filter((row) => row.outcome === 'unsupported').length;
const terminalPoints = datasets.reduce((sum, dataset) => sum + dataset.run.terminal_points, 0);
const requestedPoints = datasets.reduce((sum, dataset) => sum + dataset.run.requested_points, 0);

Expand Down
153 changes: 153 additions & 0 deletions packages/app/src/components/collectivex/CollectiveXKvTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
'use client';

import { useMemo } from 'react';

import { Badge } from '@/components/ui/badge';
import { Card } from '@/components/ui/card';
import { type DataTableColumn, DataTable } from '@/components/ui/data-table';
import { useLocale } from '@/lib/use-locale';

import { collectiveXKvCell } from './data';
import type { CollectiveXDataset, CollectiveXKvCase, CollectiveXOutcome } from './types';

type CollectiveXRunKvCase = CollectiveXKvCase & { run_id: string };

const STRINGS = {
en: {
heading: 'KV-cache transfer',
description:
'Prefill-to-decode KV handoff (2 nodes x 1 GPU, DeepSeek-V4-Pro cache as vLLM allocates it). ' +
'Paged rows move per-request layer-major descriptor lists over randomized block tables; ' +
'bulk is the single-descriptor wire ceiling. GB/s is burst-aggregate pull at the largest ISL; ' +
'b1/bmax are requests posted per burst.',
},
zh: {
heading: 'KV 缓存传输',
description:
'预填充到解码的 KV 交接(2 节点 x 1 GPU,按 vLLM 为 DeepSeek-V4-Pro 分配的缓存布局)。' +
'分页行按随机块表以逐层描述符列表搬运每个请求;bulk 为单描述符线速上限。' +
'GB/s 为最大 ISL 处按突发聚合的 pull 带宽;b1/bmax 表示每次突发提交的请求数。',
},
} as const;

const OUTCOME_CLASS: Record<CollectiveXOutcome, string> = {
success: 'border-emerald-600/40 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300',
unsupported: 'border-zinc-500/40 bg-zinc-500/10 text-zinc-700 dark:text-zinc-300',
failed: 'border-red-700/50 bg-red-700/10 text-red-800 dark:text-red-300',
invalid: 'border-red-600/40 bg-red-500/10 text-red-700 dark:text-red-300',
diagnostic: 'border-amber-600/40 bg-amber-500/10 text-amber-700 dark:text-amber-300',
pending: 'border-zinc-500/40 bg-zinc-500/5 text-muted-foreground',
};

function formatGbps(value: number | null | undefined): string {
return value === null || value === undefined ? '-' : value.toFixed(value >= 100 ? 0 : 2);
}

function cellsOf(row: CollectiveXRunKvCase) {
return {
p64b1: collectiveXKvCell(row.rows, 'paged', 64, 'min'),
p64bmax: collectiveXKvCell(row.rows, 'paged', 64, 'max'),
p16b1: collectiveXKvCell(row.rows, 'paged', 16, 'min'),
bulk: collectiveXKvCell(row.rows, 'bulk', null, 'min'),
};
}

export function CollectiveXKvTable({ datasets }: { datasets: CollectiveXDataset[] }) {
const locale = useLocale();
const strings = STRINGS[locale === 'zh' ? 'zh' : 'en'];
const rows = useMemo<CollectiveXRunKvCase[]>(
() =>
datasets.flatMap((dataset) =>
(dataset.kv ?? []).map((item) => ({ ...item, run_id: dataset.run.run_id })),
),
[datasets],
);
const columns = useMemo<DataTableColumn<CollectiveXRunKvCase>[]>(
() => [
{
header: 'Run',
cell: (row) => <span className="font-mono text-xs">#{row.run_id}</span>,
sortValue: (row) => Number(row.run_id),
className: 'whitespace-nowrap',
},
{ header: 'SKU', cell: (row) => row.sku.toUpperCase(), sortValue: (row) => row.sku },
{
header: 'Backend',
cell: (row) => row.backend,
sortValue: (row) => row.backend,
className: 'whitespace-nowrap',
},
{ header: 'Fabric', cell: (row) => row.fabric, sortValue: (row) => row.fabric },
{ header: 'Workload', cell: (row) => row.workload, sortValue: (row) => row.workload },
{ header: 'Precision', cell: (row) => row.precision, sortValue: (row) => row.precision },
{
header: 'Outcome',
cell: (row) => (
<div className="min-w-28">
<Badge variant="outline" className={OUTCOME_CLASS[row.outcome]}>
{row.outcome}
</Badge>
{(row.detail || row.reason) && (
<p className="mt-1 text-xs text-muted-foreground">{row.detail ?? row.reason}</p>
)}
</div>
),
sortValue: (row) => `${row.outcome} ${row.reason ?? ''}`,
},
{
header: 'Bulk GB/s',
cell: (row) => formatGbps(cellsOf(row).bulk?.gbps_p50),
sortValue: (row) => cellsOf(row).bulk?.gbps_p50 ?? -1,
className: 'text-right tabular-nums',
},
{
header: 'p64 GB/s b1',
cell: (row) => formatGbps(cellsOf(row).p64b1?.gbps_p50),
sortValue: (row) => cellsOf(row).p64b1?.gbps_p50 ?? -1,
className: 'text-right tabular-nums',
},
{
header: 'p64 GB/s bmax',
cell: (row) => {
const cell = cellsOf(row).p64bmax;
if (!cell) return '-';
return `${formatGbps(cell.gbps_p50)} (b${cell.batch})`;
},
sortValue: (row) => cellsOf(row).p64bmax?.gbps_p50 ?? -1,
className: 'text-right tabular-nums whitespace-nowrap',
},
{
header: 'p16 GB/s b1',
cell: (row) => formatGbps(cellsOf(row).p16b1?.gbps_p50),
sortValue: (row) => cellsOf(row).p16b1?.gbps_p50 ?? -1,
className: 'text-right tabular-nums',
},
{
header: 'Handoff ms',
cell: (row) => {
const cell = cellsOf(row).p64b1;
return cell ? cell.latency_ms.p50.toFixed(1) : '-';
},
sortValue: (row) => cellsOf(row).p64b1?.latency_ms.p50 ?? -1,
className: 'text-right tabular-nums',
},
],
[],
);
if (rows.length === 0) return null;
const measured = rows.filter((row) => row.outcome === 'success').length;
return (
<Card data-testid="collectivex-kv-table" className="min-w-0 w-full max-w-full overflow-hidden">
<h2 className="text-lg font-semibold">{strings.heading}</h2>
<p className="mt-1 text-sm text-muted-foreground">
{rows.length} cases · {measured} measured · {strings.description}
</p>
<DataTable
data={rows}
columns={columns}
testId="collectivex-kv-table-table"
analyticsPrefix="collectivex_kv"
/>
</Card>
);
}
41 changes: 40 additions & 1 deletion packages/app/src/components/collectivex/data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import {
metricValue,
seriesMatchesSelection,
type CollectiveXSeriesSelection,
collectiveXKvCell,
} from './data';
import type { CollectiveXPercentiles, CollectiveXSeries } from './types';
import type { CollectiveXKvRow, CollectiveXPercentiles, CollectiveXSeries } from './types';
import { makeCollectiveXDataset, makeCollectiveXSeries } from './test-fixture';

const dataset = makeCollectiveXDataset();
Expand Down Expand Up @@ -280,3 +281,41 @@ describe('chartPoints', () => {
expect(points).toHaveLength(scaleUp.points.length);
});
});

const kvRow = (overrides: Partial<CollectiveXKvRow>): CollectiveXKvRow => ({
kind: 'paged',
isl: 32768,
page_tokens: 64,
batch: 1,
op: 'pull',
descs: 20302,
req_bytes: 183000000,
prep_ms: 1.2,
latency_ms: { p50: 24.8, p95: 26, min: 24.1, max: 26.4, n: 24 },
gbps_p50: 7.39,
verify_passed: true,
...overrides,
});

describe('collectiveXKvCell', () => {
it('picks the largest-ISL pull row at the requested batch extreme', () => {
const rows = [
kvRow({ isl: 4096, gbps_p50: 5 }),
kvRow({ gbps_p50: 7.39 }),
kvRow({ batch: 16, gbps_p50: 15.12 }),
kvRow({ op: 'push', batch: 16, gbps_p50: 99 }),
];
expect(collectiveXKvCell(rows, 'paged', 64, 'min')?.gbps_p50).toBe(7.39);
expect(collectiveXKvCell(rows, 'paged', 64, 'max')?.gbps_p50).toBe(15.12);
});

it('returns null for an unmeasured family', () => {
expect(collectiveXKvCell([kvRow({})], 'paged', 16, 'min')).toBeNull();
expect(collectiveXKvCell([], 'bulk', null, 'min')).toBeNull();
});

it('selects bulk rows by their null page size', () => {
const rows = [kvRow({}), kvRow({ kind: 'bulk', page_tokens: null, gbps_p50: 89.41 })];
expect(collectiveXKvCell(rows, 'bulk', null, 'min')?.gbps_p50).toBe(89.41);
});
});
24 changes: 24 additions & 0 deletions packages/app/src/components/collectivex/data.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
CollectiveXChartPoint,
CollectiveXComponent,
CollectiveXKvRow,
CollectiveXMode,
CollectiveXOperation,
CollectiveXPercentile,
Expand Down Expand Up @@ -198,3 +199,26 @@ export function chartPoints(
}),
);
}

/**
* The kv table cell selector, mirroring the harness's summarize: the
* largest-ISL pull row of a (kind, page) family — the bandwidth-bound point —
* at its smallest or largest measured batch. Null when the family was not
* measured (e.g. a page size the sweep dropped).
*/
export function collectiveXKvCell(
rows: CollectiveXKvRow[],
kind: CollectiveXKvRow['kind'],
pageTokens: number | null,
batch: 'min' | 'max',
): CollectiveXKvRow | null {
const matching = rows.filter(
(row) => row.kind === kind && row.page_tokens === pageTokens && row.op === 'pull',
);
if (matching.length === 0) return null;
const isl = Math.max(...matching.map((row) => row.isl));
const atIsl = matching.filter((row) => row.isl === isl);
const pick = (better: (a: number, b: number) => boolean) =>
atIsl.reduce((best, row) => (better(row.batch, best.batch) ? row : best));
return batch === 'min' ? pick((a, b) => a < b) : pick((a, b) => a > b);
}
3 changes: 3 additions & 0 deletions packages/app/src/components/collectivex/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export type {
CollectiveXCoverage,
CollectiveXCoveragePoint,
CollectiveXDataset,
CollectiveXKvCase,
CollectiveXKvLatency,
CollectiveXKvRow,
CollectiveXMode,
CollectiveXOperation,
CollectiveXOutcome,
Expand Down
Loading