Skip to content
Draft
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
6 changes: 5 additions & 1 deletion packages/app/src/components/GlobalFilterContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
import { computeAutoSwitchDecision } from '@/lib/unofficial-run-auto-switch';
import { countCurvesByPrecision, resolveEffectivePrecisions } from '@/lib/default-precisions';
import { resolveEffectiveSequence } from '@/lib/default-sequence';
import type { AvailabilityRow, WorkflowInfoResponse } from '@/lib/api';
import type { AvailabilityRow, RunConfigRow, WorkflowInfoResponse } from '@/lib/api';

const RUNDATE_RE = /^\d{4}-\d{2}-\d{2}$/u;
const RUNID_RE = /^[A-Za-z0-9_-]{1,64}$/u;
Expand All @@ -55,6 +55,7 @@ interface RunInfo {
runDate: string;
runUrl: string;
conclusion: string | null;
runConfigs: RunConfigRow[];
changelog?: {
entries: {
config_keys: string[];
Expand Down Expand Up @@ -127,6 +128,9 @@ function buildRunInfo(data: WorkflowInfoResponse): Record<string, RunInfo> {
runDate: run.created_at,
runUrl: run.html_url ? `${run.html_url}/attempts/${run.run_attempt}` : '',
conclusion: run.conclusion,
runConfigs: (data.runConfigs ?? []).filter(
(config) => String(config.github_run_id) === String(run.github_run_id),
),
...(runChangelogs.length > 0 && {
changelog: {
entries: runChangelogs.map((c) => ({
Expand Down
2 changes: 2 additions & 0 deletions packages/app/src/components/inference/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type React from 'react';

import type { HardwareEntry } from '@/lib/constants';
import type { Model, Sequence } from '@/lib/data-mappings';
import type { RunConfigRow } from '@/lib/api';

/**
* Role of a single worker process in a multinode / disaggregated deployment.
Expand Down Expand Up @@ -689,6 +690,7 @@ export interface RunInfo {
runDate: string;
runUrl: string;
conclusion: string | null;
runConfigs?: RunConfigRow[];
changelog?: ChangelogMetadata;
}

Expand Down
7 changes: 4 additions & 3 deletions packages/app/src/components/inference/ui/ScatterGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ import {
renderKnownIssueAnnotations,
} from '@/components/inference/utils/knownIssueAnnotations';
import { matchesQuickFilters } from '@/components/inference/utils/quickFilters';
import { changelogConfigToHwKey } from '@/components/inference/utils/changelogFormatters';
import { resolveChangelogHwKeys } from '@/components/inference/utils/changelogFormatters';
import {
buildFrontierContinuations,
fitContinuationLabelBaseline,
Expand Down Expand Up @@ -737,8 +737,9 @@ const ScatterGraph = React.memo(
const hwKeys = cl.entries.flatMap((entry: any) =>
(entry.config_keys ?? entry['config-keys'] ?? [])
.filter((key: string) => selectedPrecisions.includes(key.split('-')[1]))
.map(changelogConfigToHwKey)
.filter((key: string | null): key is string => key !== null),
.flatMap((key: string) =>
resolveChangelogHwKeys(key, availableRuns[selectedRunId]?.runConfigs ?? []),
),
);
return new Set(hwKeys);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export default function WorkflowInfoDisplay({
.filter((entry) => entry.config_keys.length > 0);
return filtered.length > 0 ? { entries: filtered } : null;
})();
const selectedRunConfigs = availableRuns?.[selectedRunId]?.runConfigs ?? [];

return (
<div className="flex flex-wrap gap-2 lg:gap-4 text-muted-foreground">
Expand Down Expand Up @@ -229,7 +230,7 @@ export default function WorkflowInfoDisplay({
<div className="text-xs font-bold">Updated Configs</div>
<ul className="list-disc pl-4">
{entry.config_keys.map((key: string) => (
<li key={key}>{formatConfigKeys(key)}</li>
<li key={key}>{formatConfigKeys(key, selectedRunConfigs)}</li>
))}
</ul>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
import { describe, expect, it } from 'vitest';

import type { RunConfigRow } from '@/lib/api';

import {
changelogConfigToHwKey,
configKeyMatchesHwKey,
formatConfigKeys,
resolveChangelogHwKeys,
} from './changelogFormatters';

const kimiH200MtpConfig: RunConfigRow = {
github_run_id: 30781313910,
run_started_at: '2026-08-04T14:00:18Z',
html_url: 'https://github.com/SemiAnalysisAI/InferenceX/actions/runs/30781313910',
head_sha: '114c1bd140ba75e082100ad11f34e3cf0adf9e3d',
model: 'kimik3',
precision: 'fp4',
hardware: 'h200',
framework: 'vllm',
spec_method: 'mtp',
disagg: false,
};

describe('formatConfigKeys', () => {
it('formats a standard config key', () => {
const result = formatConfigKeys('gptoss-fp8-b200-vllm');
Expand Down Expand Up @@ -53,6 +69,12 @@ describe('formatConfigKeys', () => {
'MI355X (MoRI SGLang) DeepSeek-V4-Pro FP4',
);
});

it('derives MTP from the benchmark config when the changelog key omits it', () => {
expect(formatConfigKeys('kimik3-fp4-h200-vllm-agentic', [kimiH200MtpConfig])).toBe(
'H200 (vLLM, MTP) Kimi-K3 FP4',
);
});
});

describe('changelogConfigToHwKey', () => {
Expand Down Expand Up @@ -109,4 +131,24 @@ describe('configKeyMatchesHwKey', () => {
it('rejects completely different framework', () => {
expect(configKeyMatchesHwKey('dsr1-fp8-h200-sglang', 'h200_trt')).toBe(false);
});

it('matches the benchmark-derived spec method when the changelog key is incomplete', () => {
expect(
configKeyMatchesHwKey('kimik3-fp4-h200-vllm-agentic', 'h200_vllm_mtp', [kimiH200MtpConfig]),
).toBe(true);
});
});

describe('resolveChangelogHwKeys', () => {
it('treats run content as authoritative over a stale MTP key suffix', () => {
expect(
resolveChangelogHwKeys('kimik3-fp4-h200-vllm-agentic-mtp', [
{ ...kimiH200MtpConfig, spec_method: 'none' },
]),
).toEqual(['h200_vllm']);
});

it('falls back to the changelog key for historical runs without config coverage', () => {
expect(resolveChangelogHwKeys('dsr1-fp8-h200-sglang-mtp')).toEqual(['h200_sglang_mtp']);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,91 @@ import {

import { type Precision, MODEL_PREFIX_MAPPING, getPrecisionLabel } from '@/lib/data-mappings';
import { getHardwareConfig } from '@/lib/constants';
import { buildAvailabilityHwKey } from '@/lib/chart-utils';
import type { RunConfigRow } from '@/lib/api';
import { getDisplayLabel } from '@/lib/utils';

const CHANGELOG_FRAMEWORK_KEYS = [
...Object.keys(FW_REGISTRY),
...Object.keys(FRAMEWORK_ALIASES),
].toSorted((a, b) => b.length - a.length);

interface ChangelogConfigScope {
model: string;
precision: string;
hardware: string;
framework: string;
}

function changelogConfigScope(configKey: string): ChangelogConfigScope | null {
const parts = configKey.toLowerCase().split('-');
const model = parts[0];
const precision = parts[1];
const hardware = parts[2];
const remainder = parts.slice(3).join('-');
if (!model || !precision || !hardware || !remainder) return null;

const framework = CHANGELOG_FRAMEWORK_KEYS.find(
(candidate) => remainder === candidate || remainder.startsWith(`${candidate}-`),
);
if (!framework) return null;

return {
model,
precision,
hardware,
framework: resolveFrameworkAlias(framework),
};
}

function matchingRunConfigs(configKey: string, runConfigs: RunConfigRow[]): RunConfigRow[] {
const scope = changelogConfigScope(configKey);
if (!scope) return [];

return runConfigs.filter(
(config) =>
config.model === scope.model &&
config.precision === scope.precision &&
config.hardware === scope.hardware &&
resolveFrameworkAlias(config.framework) === scope.framework,
);
}

/**
* Resolve a changelog key to chart hardware keys using configs actually emitted
* by the workflow run. The text key scopes the changed model/config family; the
* benchmark rows remain authoritative for spec decoding and disaggregation.
*/
export function resolveChangelogHwKeys(
configKey: string,
runConfigs: RunConfigRow[] = [],
): string[] {
const resolved = matchingRunConfigs(configKey, runConfigs).map((config) =>
buildAvailabilityHwKey(config.hardware, config.framework, config.spec_method, config.disagg),
);
if (resolved.length > 0) return [...new Set(resolved)];

const fallback = changelogConfigToHwKey(configKey);
return fallback ? [fallback] : [];
}

/**
* Convert a changelog config key into the canonical hardware key used by chart
* points and the legend. Agentic config keys append scenario details such as
* `agentic`, `hicache`, and `pcp` after the serving framework; those are not
* framework labels and must not become part of the legend identity.
*/
export function changelogConfigToHwKey(configKey: string): string | null {
const parts = configKey.toLowerCase().split('-');
const gpu = parts[2];
const remainder = parts.slice(3).join('-');
if (!gpu || !remainder) return null;
const scope = changelogConfigScope(configKey);
if (!scope) return null;

const remainder = configKey.toLowerCase().split('-').slice(3).join('-');
const framework = CHANGELOG_FRAMEWORK_KEYS.find(
(candidate) => remainder === candidate || remainder.startsWith(`${candidate}-`),
);
if (!framework) return null;

)!;
const trailingParts = remainder.slice(framework.length).split('-').filter(Boolean);
const specSuffix = trailingParts.includes('mtp') ? '_mtp' : '';
return `${gpu}_${resolveFrameworkAlias(framework)}${specSuffix}`;
return `${scope.hardware}_${scope.framework}${specSuffix}`;
}

export function formatChangelogDescription(desc: string | string[]) {
Expand All @@ -59,19 +117,23 @@ export function formatChangelogDescription(desc: string | string[]) {
}

/**
* Check if a changelog config key matches a hwKey.
* Normalizes both to hyphen-separated form for comparison.
* Check whether a changelog scope includes a chart hardware key, preferring
* benchmark-derived run configs when they are available.
*/
export function configKeyMatchesHwKey(configKey: string, hwKey: string): boolean {
return changelogConfigToHwKey(configKey) === hwKey;
export function configKeyMatchesHwKey(
configKey: string,
hwKey: string,
runConfigs: RunConfigRow[] = [],
): boolean {
return resolveChangelogHwKeys(configKey, runConfigs).includes(hwKey);
}

export function formatConfigKeys(key: string) {
export function formatConfigKeys(key: string, runConfigs: RunConfigRow[] = []) {
const parts = key.split('-');
const model = parts[0];
const precision = parts[1];
const modelLabel = MODEL_PREFIX_MAPPING[model];
const hwKey = changelogConfigToHwKey(key);
const hwKey = resolveChangelogHwKeys(key, runConfigs)[0];

if (!hwKey) {
const gpu = parts[2]?.toUpperCase() ?? '';
Expand Down