diff --git a/packages/app/src/components/evaluation/RecipeComparison.tsx b/packages/app/src/components/evaluation/RecipeComparison.tsx index 6d0f13a1d..9886d4bdf 100644 --- a/packages/app/src/components/evaluation/RecipeComparison.tsx +++ b/packages/app/src/components/evaluation/RecipeComparison.tsx @@ -145,6 +145,7 @@ const COLUMNS: DataTableColumn[] = [ export default function RecipeComparison() { const { selectedModel } = useGlobalFilters(); const [category, setCategory] = useState('all'); + const [topology, setTopology] = useState('all'); // Always use the no-date "latest state across all dates" path. The recipe // table is meant to surface current recipe variants, not historical data — @@ -172,12 +173,29 @@ export default function RecipeComparison() { return TECHNIQUE_CATEGORIES.filter((c) => c.value === 'all' || seen.has(c.value)); }, [allRows]); - const rows = useMemo(() => { - if (category === 'all') return allRows; - // Always include baseline rows in any non-"all" filter so the user has a - // reference variant on the page if one exists in the group. - return allRows.filter((r) => r.category === category || r.isBaseline); - }, [allRows, category]); + // Topology chips — one per distinct deployment shape (e.g. 1× (TP=1), + // 2× (TP=2)). Sorted by total GPU count ascending so the chip order is + // intuitive: smaller deployments first. + const availableTopologies = useMemo(() => { + const byKey = new Map(); + for (const r of allRows) { + if (byKey.has(r.topology)) continue; + byKey.set(r.topology, { label: r.topology, total: r.numPrefillGpu + r.numDecodeGpu }); + } + return [...byKey.values()].toSorted((a, b) => a.total - b.total); + }, [allRows]); + + const rows = useMemo( + () => + allRows.filter((r) => { + if (topology !== 'all' && r.topology !== topology) return false; + // Always include baseline rows in any non-"all" category filter so the + // user has a reference variant on the page if one exists in the group. + if (category !== 'all' && r.category !== category && !r.isBaseline) return false; + return true; + }), + [allRows, category, topology], + ); const loading = bmkLoading || evalLoading; const error = bmkError ?? evalError; @@ -218,6 +236,45 @@ export default function RecipeComparison() { })} )} + {availableTopologies.length > 1 && ( +
+ Filter by topology: + + {availableTopologies.map((t) => { + const active = topology === t.label; + return ( + + ); + })} +
+ )} {error && (