Skip to content

Commit 75c6467

Browse files
cquil11claude
andauthored
fix(replay): stamp frame points with the playhead date so series render as one line (#620)
ScatterGraph scopes Pareto frontiers and line paths per point.date (needed for the comparison-date overlay), but replay frames copied each config's template point verbatim — carrying that config's first-observation date. Configs introduced on different dates therefore landed in different date buckets within one frame, splitting a single hardware series into several same-colored roofline segments with duplicated line labels ("one line diverging into two") the moment the playhead crossed a newer config's first date. Stamping every frame point with the playhead's current date restores one frontier and one path per (hw, precision), which matches what a replay frame is: a single point in time. 中文:replay(时间回放)图表中,同一硬件系列会分裂成多条同色曲线并出现重复的 曲线标签。原因是每帧数据沿用了各 config 模板点的首次观测日期,而 ScatterGraph 按 point.date 分桶计算 Pareto 前沿并分段绘制路径。现将每帧所有数据点统一标记为 播放头当前日期,使每个(硬件、精度)系列在单帧内只生成一条前沿曲线,符合 "一帧即一个时间点"的语义。附带回归单元测试。 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 61128b4 commit 75c6467

2 files changed

Lines changed: 27 additions & 2 deletions

File tree

packages/app/src/components/inference/replay/__tests__/replayFrameData.test.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ const baseTemplate = {
1717
precision: 'fp8',
1818
tp: 8,
1919
conc: 64,
20+
// Deliberately NOT one of the timeline dates: templates carry each config's
21+
// first-observation date, which buildFrameData must override per frame.
22+
date: '2025-08-15',
2023
} as unknown as InferenceData;
2124

2225
function makeTimeline(): ReplayTimeline {
@@ -38,7 +41,7 @@ function makeTimeline(): ReplayTimeline {
3841
configId: 'b',
3942
hwKey: 'h100',
4043
precision: 'fp8',
41-
template: { ...baseTemplate, hwKey: 'h100' } as InferenceData,
44+
template: { ...baseTemplate, hwKey: 'h100', date: '2025-08-20' } as InferenceData,
4245
// Stays invisible across the first two steps so a true "omits invisible
4346
// configs" assertion is meaningful — `interpolateAtStep` pops a config
4447
// in for the *whole* invisible→visible segment, so we need both
@@ -243,6 +246,22 @@ describe('buildFrameData', () => {
243246
}
244247
});
245248

249+
// Regression: ScatterGraph scopes Pareto frontiers and line paths per
250+
// point.date, so a frame mixing template (first-observation) dates renders
251+
// one hardware series as several same-colored lines with duplicate labels.
252+
it('stamps every point with the playhead date, not the template first-observation date', () => {
253+
const t = makeTimeline();
254+
const atStart = buildFrameData(t, 0);
255+
expect(atStart.length).toBeGreaterThan(0);
256+
for (const d of atStart) expect(d.date).toBe('2025-09-01');
257+
258+
const atEnd = buildFrameData(t, 1);
259+
// Both configs are visible at the last step and carry distinct template
260+
// dates — the frame must still present a single uniform date.
261+
expect(atEnd).toHaveLength(2);
262+
for (const d of atEnd) expect(d.date).toBe('2025-09-03');
263+
});
264+
246265
it('returns empty when the timeline has zero configs', () => {
247266
const empty: ReplayTimeline = {
248267
dates: ['2025-09-01'],

packages/app/src/components/inference/replay/replayFrameData.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@ import { interpolateAtStep } from './interpolateAtTime';
55

66
export function buildFrameData(timeline: ReplayTimeline, fraction: number): InferenceData[] {
77
const idxFloat = stepFloatAtFraction(fraction, timeline.dates.length);
8+
// A replay frame is a single point in time, so every point gets the
9+
// playhead's date. Each template keeps its config's first-observation date,
10+
// and ScatterGraph scopes Pareto frontiers and line paths per date — leaving
11+
// mixed template dates in one frame splits a hardware series into several
12+
// same-colored lines (with duplicated line labels) mid-replay.
13+
const frameDate = dateAtFraction(timeline, fraction);
814
const out: InferenceData[] = [];
915
for (const c of timeline.configs) {
1016
const r = interpolateAtStep(c.stepValues, idxFloat);
1117
if (!r.visible) continue;
12-
out.push({ ...c.template, x: r.x, y: r.y });
18+
out.push({ ...c.template, x: r.x, y: r.y, date: frameDate });
1319
}
1420
return out;
1521
}

0 commit comments

Comments
 (0)