Skip to content

Commit 115da5f

Browse files
authored
fix(agentic): align changelog configs with legend (#608)
Reuse the canonical chart hardware-key and display-label path for changelog config formatting and modified-config highlighting. Add regression coverage for agentic HiCache suffixes. 中文:统一变更记录与图例中的配置名称;复用规范化硬件 key 和显示名称逻辑,并修复 Agentic HiCache 配置的红色高亮。新增对应的回归测试。
1 parent ae36740 commit 115da5f

3 files changed

Lines changed: 86 additions & 21 deletions

File tree

packages/app/src/components/inference/ui/ScatterGraph.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ import {
8181
renderKnownIssueAnnotations,
8282
} from '@/components/inference/utils/knownIssueAnnotations';
8383
import { matchesQuickFilters } from '@/components/inference/utils/quickFilters';
84+
import { changelogConfigToHwKey } from '@/components/inference/utils/changelogFormatters';
8485

8586
// Greedy label-collision avoidance.
8687
// Each candidate is the y-position of the FIRST baseline (relative to point
@@ -634,16 +635,17 @@ const ScatterGraph = React.memo(
634635

635636
// --- Changelog ---
636637
const changelog = availableRuns ? availableRuns[selectedRunId]?.changelog || null : null;
637-
const highlightConfigSuffixes = useMemo(() => {
638+
const highlightedHwKeys = useMemo(() => {
638639
if (availableRuns) {
639640
const cl = availableRuns[selectedRunId]?.changelog;
640641
if (cl) {
641-
const suffixes = cl.entries.flatMap((entry: any) =>
642+
const hwKeys = cl.entries.flatMap((entry: any) =>
642643
(entry.config_keys ?? entry['config-keys'] ?? [])
643644
.filter((key: string) => selectedPrecisions.includes(key.split('-')[1]))
644-
.map((key: string) => key.split('-').slice(2).join('-')),
645+
.map(changelogConfigToHwKey)
646+
.filter((key: string | null): key is string => key !== null),
645647
);
646-
return new Set(suffixes);
648+
return new Set(hwKeys);
647649
}
648650
}
649651
return new Set<string>();
@@ -2913,7 +2915,7 @@ const ScatterGraph = React.memo(
29132915
label: getDisplayLabel(hwConfig),
29142916
color: resolveColor(key),
29152917
title: hwConfig.gpu,
2916-
isHighlighted: highlightConfigSuffixes.has(key.replaceAll('_', '-')),
2918+
isHighlighted: highlightedHwKeys.has(key),
29172919
hw: key,
29182920
isActive: showAllHardwareTypes ? true : effectiveOfficialHwTypes.has(key),
29192921
onClick: showAllHardwareTypes

packages/app/src/components/inference/utils/changelogFormatters.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { describe, expect, it } from 'vitest';
22

3-
import { configKeyMatchesHwKey, formatConfigKeys } from './changelogFormatters';
3+
import {
4+
changelogConfigToHwKey,
5+
configKeyMatchesHwKey,
6+
formatConfigKeys,
7+
} from './changelogFormatters';
48

59
describe('formatConfigKeys', () => {
610
it('formats a standard config key', () => {
@@ -43,6 +47,26 @@ describe('formatConfigKeys', () => {
4347
expect(result).toContain('TRTLLM');
4448
expect(result).toContain('FP4');
4549
});
50+
51+
it('uses the legend framework label for an agentic HiCache config', () => {
52+
expect(formatConfigKeys('dsv4-fp4-mi355x-mori-sglang-agentic-hicache')).toBe(
53+
'MI355X (MoRI SGLang) DeepSeek-V4-Pro FP4',
54+
);
55+
});
56+
});
57+
58+
describe('changelogConfigToHwKey', () => {
59+
it('strips agentic scenario and cache-backend suffixes from the legend identity', () => {
60+
expect(changelogConfigToHwKey('dsv4-fp4-mi355x-mori-sglang-agentic-hicache')).toBe(
61+
'mi355x_mori-sglang',
62+
);
63+
});
64+
65+
it('keeps a trailing MTP spec method while dropping agentic metadata', () => {
66+
expect(changelogConfigToHwKey('dsv4-fp4-mi355x-sglang-agentic-hicache-mtp')).toBe(
67+
'mi355x_sglang_mtp',
68+
);
69+
});
4670
});
4771

4872
describe('configKeyMatchesHwKey', () => {
@@ -68,6 +92,12 @@ describe('configKeyMatchesHwKey', () => {
6892
expect(configKeyMatchesHwKey('dsr1-fp8-mi355x-sglang-disagg', 'mi355x_mori-sglang')).toBe(true);
6993
});
7094

95+
it('matches an agentic HiCache changelog key to the framework-only legend key', () => {
96+
expect(
97+
configKeyMatchesHwKey('dsv4-fp4-mi355x-mori-sglang-agentic-hicache', 'mi355x_mori-sglang'),
98+
).toBe(true);
99+
});
100+
71101
it('matches sglang framework', () => {
72102
expect(configKeyMatchesHwKey('gptoss-fp8-mi300x-sglang', 'mi300x_sglang')).toBe(true);
73103
});

packages/app/src/components/inference/utils/changelogFormatters.tsx

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,40 @@
11
import {
2-
resolveFrameworkAliasesInString,
2+
FRAMEWORK_ALIASES,
3+
FW_REGISTRY,
4+
resolveFrameworkAlias,
35
resolveFrameworkPartLabel,
46
} from '@semianalysisai/inferencex-constants';
57

68
import { type Precision, MODEL_PREFIX_MAPPING, getPrecisionLabel } from '@/lib/data-mappings';
7-
import { getFrameworkLabel } from '@/lib/utils';
9+
import { getHardwareConfig } from '@/lib/constants';
10+
import { getDisplayLabel } from '@/lib/utils';
11+
12+
const CHANGELOG_FRAMEWORK_KEYS = [
13+
...Object.keys(FW_REGISTRY),
14+
...Object.keys(FRAMEWORK_ALIASES),
15+
].toSorted((a, b) => b.length - a.length);
16+
17+
/**
18+
* Convert a changelog config key into the canonical hardware key used by chart
19+
* points and the legend. Agentic config keys append scenario details such as
20+
* `agentic`, `hicache`, and `pcp` after the serving framework; those are not
21+
* framework labels and must not become part of the legend identity.
22+
*/
23+
export function changelogConfigToHwKey(configKey: string): string | null {
24+
const parts = configKey.toLowerCase().split('-');
25+
const gpu = parts[2];
26+
const remainder = parts.slice(3).join('-');
27+
if (!gpu || !remainder) return null;
28+
29+
const framework = CHANGELOG_FRAMEWORK_KEYS.find(
30+
(candidate) => remainder === candidate || remainder.startsWith(`${candidate}-`),
31+
);
32+
if (!framework) return null;
33+
34+
const trailingParts = remainder.slice(framework.length).split('-').filter(Boolean);
35+
const specSuffix = trailingParts.includes('mtp') ? '_mtp' : '';
36+
return `${gpu}_${resolveFrameworkAlias(framework)}${specSuffix}`;
37+
}
838

939
export function formatChangelogDescription(desc: string | string[]) {
1040
if (typeof desc === 'string') {
@@ -33,23 +63,26 @@ export function formatChangelogDescription(desc: string | string[]) {
3363
* Normalizes both to hyphen-separated form for comparison.
3464
*/
3565
export function configKeyMatchesHwKey(configKey: string, hwKey: string): boolean {
36-
const gpuAndFramework = resolveFrameworkAliasesInString(configKey.split('-').slice(2).join('-'));
37-
const normalizedHwKey = hwKey.replaceAll('_', '-');
38-
return gpuAndFramework === normalizedHwKey;
66+
return changelogConfigToHwKey(configKey) === hwKey;
3967
}
4068

4169
export function formatConfigKeys(key: string) {
4270
const parts = key.split('-');
4371
const model = parts[0];
4472
const precision = parts[1];
45-
const gpu = parts[2];
46-
const framework = parts.slice(3).join('-');
47-
// Strip -mtp suffix before lookup; MTP is shown separately
48-
const isMtp = framework.endsWith('-mtp');
49-
const baseFramework = isMtp ? framework.slice(0, -4) : framework;
50-
const baseLabel = getFrameworkLabel(baseFramework);
51-
// M3's `mtp` spec token renders as "EAGLE"; every other model keeps "MTP".
52-
const mtpLabel = resolveFrameworkPartLabel(MODEL_PREFIX_MAPPING[model], 'mtp');
53-
const frameworkLabel = isMtp ? `${baseLabel}, ${mtpLabel}` : baseLabel;
54-
return `${gpu.toUpperCase()} (${frameworkLabel}) ${MODEL_PREFIX_MAPPING[model]} ${getPrecisionLabel(precision as Precision)}`;
73+
const modelLabel = MODEL_PREFIX_MAPPING[model];
74+
const hwKey = changelogConfigToHwKey(key);
75+
76+
if (!hwKey) {
77+
const gpu = parts[2]?.toUpperCase() ?? '';
78+
const framework = parts.slice(3).join('-');
79+
const frameworkLabel = resolveFrameworkPartLabel(modelLabel, framework);
80+
return `${gpu} (${frameworkLabel}) ${modelLabel} ${getPrecisionLabel(precision as Precision)}`;
81+
}
82+
83+
// Use the same hardware entry builder and display combiner as the legend so
84+
// aliases, compound framework names, and model-specific spec labels cannot
85+
// drift between the two surfaces.
86+
const hardwareLabel = getDisplayLabel(getHardwareConfig(hwKey, modelLabel));
87+
return `${hardwareLabel} ${modelLabel} ${getPrecisionLabel(precision as Precision)}`;
5588
}

0 commit comments

Comments
 (0)