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
44 changes: 36 additions & 8 deletions packages/app/cypress/component/scatter-graph.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -558,13 +558,19 @@ describe('ScatterGraph', () => {
runUrl,
};
const exclusion = buildExclusion([
{ suffix: null, stripPrefixes: ['dynamo-', 'mori-', 'llmd-', 'mooncake-'] },
{
suffix: null,
stripPrefixes: ['dynamo-', 'mori-', 'llmd-', 'mooncake-'],
scope: 'hardware',
},
]);
const namespacedExclusion = {
familyOf: (key: string) =>
exclusion.familyOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
groupOf: (key: string) =>
exclusion.groupOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
scopesOf: (key: string) =>
exclusion.scopesOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
};
const blockedToggle = cy.stub().as('blockedComparisonToggle').returns(null);

Expand Down Expand Up @@ -649,13 +655,19 @@ describe('ScatterGraph', () => {
runUrl,
};
const exclusion = buildExclusion([
{ suffix: null, stripPrefixes: ['dynamo-', 'mori-', 'llmd-', 'mooncake-'] },
{
suffix: null,
stripPrefixes: ['dynamo-', 'mori-', 'llmd-', 'mooncake-'],
scope: 'hardware',
},
]);
const namespacedExclusion = {
familyOf: (key: string) =>
exclusion.familyOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
groupOf: (key: string) =>
exclusion.groupOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
scopesOf: (key: string) =>
exclusion.scopesOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
};

mountWithProviders(
Expand Down Expand Up @@ -845,7 +857,7 @@ describe('ChartDisplay engine comparison guard', () => {
cy.get('@setLocalOfficialOverride').should('not.have.been.called');
});

it('keeps cross-engine AgentX STP rows out of table mode', () => {
it('keeps same-hardware cross-engine AgentX STP rows out of table mode', () => {
const chartDefinition = createMockChartDefinition({ chartType: 'interactivity' });
const sglangRow = createMockInferenceData({
hwKey: 'b200_sglang',
Expand All @@ -854,13 +866,17 @@ describe('ChartDisplay engine comparison guard', () => {
precision: Precision.FP4,
});
const vllmRow = createMockInferenceData({
hwKey: 'h100_vllm',
hwKey: 'b200_vllm',
hw: 'Official vLLM',
model: Model.DeepSeek_V4_Pro,
precision: Precision.FP4,
});
const exclusion = buildExclusion([
{ suffix: null, stripPrefixes: ['dynamo-', 'mori-', 'llmd-', 'mooncake-'] },
{
suffix: null,
stripPrefixes: ['dynamo-', 'mori-', 'llmd-', 'mooncake-'],
scope: 'hardware',
},
Comment thread
cursor[bot] marked this conversation as resolved.
]);
const resolveSelection = (proposed: Set<string>, prev = new Set<string>()) =>
resolveExclusionGroups(proposed, prev, exclusion, 'keep-sticky');
Expand All @@ -879,7 +895,7 @@ describe('ChartDisplay engine comparison guard', () => {
selectedSequence: Sequence.AgenticTraces,
selectedXAxisMode: 'interactivity',
activeHwTypes: new Set(['b200_sglang']),
hwTypesWithData: new Set(['b200_sglang', 'h100_vllm']),
hwTypesWithData: new Set(['b200_sglang', 'b200_vllm']),
resolveComparisonSelection: resolveSelection,
},
globalFilters: {
Expand Down Expand Up @@ -911,13 +927,19 @@ describe('ChartDisplay engine comparison guard', () => {
run_url: runUrl,
});
const exclusion = buildExclusion([
{ suffix: null, stripPrefixes: ['dynamo-', 'mori-', 'llmd-', 'mooncake-'] },
{
suffix: null,
stripPrefixes: ['dynamo-', 'mori-', 'llmd-', 'mooncake-'],
scope: 'hardware',
},
]);
const namespacedExclusion = {
familyOf: (key: string) =>
exclusion.familyOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
groupOf: (key: string) =>
exclusion.groupOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
scopesOf: (key: string) =>
exclusion.scopesOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
};
const resolveSelection = (proposed: Set<string>, prev = new Set<string>()) =>
resolveExclusionGroups(proposed, prev, namespacedExclusion, 'keep-sticky');
Expand Down Expand Up @@ -1014,13 +1036,19 @@ describe('ChartDisplay engine comparison guard', () => {
it('commits a new table overlay scope and preserves an explicit empty selection', () => {
const chartDefinition = createMockChartDefinition({ chartType: 'interactivity' });
const exclusion = buildExclusion([
{ suffix: null, stripPrefixes: ['dynamo-', 'mori-', 'llmd-', 'mooncake-'] },
{
suffix: null,
stripPrefixes: ['dynamo-', 'mori-', 'llmd-', 'mooncake-'],
scope: 'hardware',
},
]);
const namespacedExclusion = {
familyOf: (key: string) =>
exclusion.familyOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
groupOf: (key: string) =>
exclusion.groupOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
scopesOf: (key: string) =>
exclusion.scopesOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
};
const resolveSelection = (proposed: Set<string>, prev = new Set<string>()) =>
resolveExclusionGroups(proposed, prev, namespacedExclusion, 'keep-sticky');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { describe, expect, it } from 'vitest';

import { describeEngineComparisonConflict } from './engine-comparison-conflict-toast';

describe('describeEngineComparisonConflict', () => {
it('describes partial removal without assuming hardware scope', () => {
const message = describeEngineComparisonConflict(
{
kind: 'resolved',
kept: ['vllm'],
dropped: [],
partial: ['sglang'],
},
'en',
);

expect(message).toContain(
'Disabled conflicting SGLang configs while compatible SGLang configs remain shown',
);
expect(message).not.toContain('SKU');
expect(message).not.toContain('Kept SGLang');
expect(message).not.toContain('removed SGLang');
});

it('preserves the whole-family resolution message', () => {
expect(
describeEngineComparisonConflict(
{
kind: 'resolved',
kept: ['sglang'],
dropped: ['vllm'],
partial: [],
},
'en',
),
).toContain('Kept SGLang and removed vLLM configs');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { BottomToast } from '@/components/ui/bottom-toast';
*/
export type EngineComparisonConflictDetail =
| { kind: 'blocked'; attempted: string; existing: string | null }
| { kind: 'resolved'; kept: string[]; dropped: string[] };
| { kind: 'resolved'; kept: string[]; dropped: string[]; partial: string[] };

function familyLabel(family: string): string {
return FRAMEWORK_LABELS[family] ?? family;
Expand All @@ -37,7 +37,10 @@ function joinListZh(parts: string[]): string {
return `${parts.slice(0, -1).join('、')}和 ${parts.at(-1)}`;
}

function describe(detail: EngineComparisonConflictDetail, locale: Locale): string {
export function describeEngineComparisonConflict(
detail: EngineComparisonConflictDetail,
locale: Locale,
): string {
if (locale === 'zh') return describeZh(detail);
if (detail.kind === 'blocked') {
const attempted = familyLabel(detail.attempted);
Expand All @@ -49,6 +52,11 @@ function describe(detail: EngineComparisonConflictDetail, locale: Locale): strin
}
const kept = [...detail.kept].toSorted().map(familyLabel);
const dropped = [...detail.dropped].toSorted().map(familyLabel);
const partial = [...detail.partial].toSorted().map(familyLabel);
if (partial.length > 0) {
const removed = dropped.length > 0 ? ` Removed ${joinList(dropped)} configs.` : '';
return `Only compatible engine configurations can be shown together in this view.${removed} Disabled conflicting ${joinList(partial)} configs while compatible ${joinList(partial)} configs remain shown.`;
}
if (kept.length > 0) {
return `Only compatible engine families can be shown together in this view. Kept ${joinList(kept)} and removed ${joinList(dropped)} configs.`;
}
Expand All @@ -69,6 +77,11 @@ function describeZh(detail: EngineComparisonConflictDetail): string {
}
const kept = [...detail.kept].toSorted().map(familyLabel);
const dropped = [...detail.dropped].toSorted().map(familyLabel);
const partial = [...detail.partial].toSorted().map(familyLabel);
if (partial.length > 0) {
const removed = dropped.length > 0 ? ` 已移除 ${joinListZh(dropped)} 配置。` : '';
return `此视图只能同时显示相互兼容的引擎配置。${removed}已禁用冲突的 ${joinListZh(partial)} 配置,同时保留兼容的 ${joinListZh(partial)} 配置。`;
}
if (kept.length > 0) {
return `此视图只能同时显示相互兼容的引擎系列。已保留 ${joinListZh(kept)},并移除 ${joinListZh(dropped)} 配置。`;
}
Expand Down Expand Up @@ -101,6 +114,7 @@ export function EngineComparisonConflictToast({ detail, onDismiss }: Props) {
existing: detail.kind === 'blocked' ? detail.existing : null,
kept: detail.kind === 'resolved' ? detail.kept : null,
dropped: detail.kind === 'resolved' ? detail.dropped : null,
partial: detail.kind === 'resolved' ? detail.partial : null,
});
}, [detail]);

Expand All @@ -112,7 +126,7 @@ export function EngineComparisonConflictToast({ detail, onDismiss }: Props) {
testId="engine-comparison-conflict-toast"
icon={<AlertTriangle className="text-amber-500" />}
title={TITLES[locale]}
description={describe(detail, locale)}
description={describeEngineComparisonConflict(detail, locale)}
onDismiss={onDismiss}
/>
);
Expand Down
18 changes: 14 additions & 4 deletions packages/app/src/components/inference/InferenceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,12 @@ export function InferenceProvider({
if (!selectedGpuResolution) return;
setSelectedGpuState(selectedGpuResolution.selection);
setUrlParam('i_gpus', selectedGpuResolution.selection.join(','));
if (selectedGpuResolution.dropped.length > 0) {
if (selectedGpuResolution.dropped.length > 0 || selectedGpuResolution.partial.length > 0) {
setEngineConflict({
kind: 'resolved',
kept: selectedGpuResolution.kept,
dropped: selectedGpuResolution.dropped,
partial: selectedGpuResolution.partial,
});
}
}, [selectedGpuResolution, setUrlParam]);
Expand Down Expand Up @@ -801,12 +802,16 @@ export function InferenceProvider({
exclusion.familyOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
groupOf: (key: string) =>
exclusion.groupOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
scopesOf: (key: string) =>
exclusion.scopesOf(key.startsWith('overlay:') ? key.slice('overlay:'.length) : key),
}
: null,
[exclusion],
);
const activeHwTypesRef = useRef(activeHwTypes);
activeHwTypesRef.current = activeHwTypes;
const preferredHwTypesRef = useRef(activeHwTypes);
if (activeHwTypes.size > 1) preferredHwTypesRef.current = activeHwTypes;
const exclusionRef = useRef(comparisonExclusion);
exclusionRef.current = comparisonExclusion;
const exclusionPolicyRef = useRef(exclusionPolicy);
Expand All @@ -827,7 +832,7 @@ export function InferenceProvider({
(prev: Set<string>, item: string, allItems: Set<string>): Set<string> | null => {
const currentExclusion = exclusionRef.current;
const toggleUniverse = currentExclusion
? effectiveLegendItems(allItems, prev, currentExclusion)
? effectiveLegendItems(allItems, prev, currentExclusion, preferredHwTypesRef.current)
: allItems;
if (currentExclusion) {
const decision = resolveExclusionToggle(
Expand All @@ -845,9 +850,14 @@ export function InferenceProvider({
});
return null;
}
if (decision.kind === 'silent-resolve') return decision.result;
if (decision.kind === 'silent-resolve') {
if (decision.result.size > 1) preferredHwTypesRef.current = decision.result;
return decision.result;
}
}
return computeToggle(prev, item, toggleUniverse);
const result = computeToggle(prev, item, toggleUniverse);
if (result.size > 1) preferredHwTypesRef.current = result;
return result;
},
[],
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest';

import { resolveExclusionToggle } from '@/lib/exclusion';
import { Model, Sequence } from '@/lib/data-mappings';

import { comparisonExclusion } from './comparison-exclusion';
Expand All @@ -12,6 +13,87 @@ describe('comparisonExclusion', () => {
expect(exclusion?.familyOf('b200_sglang')).toBe('sglang');
});

it.each([
{
name: 'blocks Agentic STP engines on the same SKU',
sequence: Sequence.AgenticTraces,
active: 'b200_sglang',
candidate: 'b200_vllm',
expected: 'block',
},
{
name: 'allows Agentic STP engines on different SKUs',
sequence: Sequence.AgenticTraces,
active: 'b200_sglang',
candidate: 'mi355x_vllm',
expected: 'fallthrough',
},
{
name: 'blocks Agentic MTP added to cross-engine STP on the same SKU',
sequence: Sequence.AgenticTraces,
active: 'b200_sglang',
candidate: 'b200_vllm_mtp',
expected: 'block',
},
{
name: 'blocks Agentic STP added to cross-engine MTP on the same SKU',
sequence: Sequence.AgenticTraces,
active: 'b200_vllm_mtp',
candidate: 'b200_sglang',
expected: 'block',
},
{
name: 'allows Agentic STP and MTP engines on different SKUs',
sequence: Sequence.AgenticTraces,
active: 'b200_sglang',
candidate: 'mi355x_vllm_mtp',
expected: 'fallthrough',
},
{
name: 'allows Agentic STP and MTP from the same engine',
sequence: Sequence.AgenticTraces,
active: 'b200_vllm',
candidate: 'b200_vllm_mtp',
expected: 'fallthrough',
},
{
name: 'blocks Agentic cross-engine MTP globally',
sequence: Sequence.AgenticTraces,
active: 'b200_sglang_mtp',
candidate: 'mi355x_vllm_mtp',
expected: 'block',
},
{
name: 'allows fixed-sequence STP engines on the same SKU',
sequence: Sequence.OneK_OneK,
active: 'b200_sglang',
candidate: 'b200_vllm',
expected: 'fallthrough',
},
{
name: 'blocks fixed-sequence cross-engine MTP globally',
sequence: Sequence.OneK_OneK,
active: 'b200_sglang_mtp',
candidate: 'mi355x_vllm_mtp',
expected: 'block',
},
] as const)('$name', ({ sequence, active, candidate, expected }) => {
const exclusion = comparisonExclusion(Model.DeepSeek_V4_Pro, sequence, false)!;
const decision = resolveExclusionToggle(
new Set([active]),
candidate,
new Set([active, candidate]),
exclusion,
'keep-sticky',
);

expect(decision.kind).toBe(expected);
});

it('does not create a guard outside configured models and scenarios', () => {
expect(comparisonExclusion(Model.Llama3_3_70B, Sequence.OneK_OneK, false)).toBeNull();
});

it('disables the engine-family guard for unofficial previews', () => {
expect(comparisonExclusion(Model.DeepSeek_V4_Pro, Sequence.AgenticTraces, true)).toBeNull();
});
Expand Down
8 changes: 5 additions & 3 deletions packages/app/src/lib/data-mappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,17 @@ const MTP_ENGINE_EXCLUSION: ExclusionSpec[] = [
];

/**
* AgentX STP exclusion: unsuffixed standard-token configs from different engine
* families can't be active together. Fixed-sequence STP comparisons remain
* available; this rule is attached only to the Agentic Traces sequence.
* AgentX STP exclusion: unsuffixed standard-token configs for the same hardware
* SKU can't mix engine families. Different hardware may use different engines
* on one graph. Fixed-sequence STP comparisons remain available; this rule is
* attached only to the Agentic Traces sequence.
*/
const AGENTIC_STP_ENGINE_EXCLUSION: ExclusionSpec[] = [
{
suffix: null,
stripPrefixes: ['dynamo-', 'mori-', 'llmd-', 'mooncake-'],
groupAliases: { atom: 'sglang' },
scope: 'hardware',
},
];

Expand Down
Loading
Loading