Skip to content
Open
24 changes: 17 additions & 7 deletions apps/performance_dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,15 @@ Trend charts show metric-specific axes and exact point details on hover/focus:
- commit SHA
- run source
- stored status
- canonical comparison status and reason
- baseline status
- baseline eligibility
- PR number, branch, and Buildkite URL when present

The latest status table uses the stored JSON `success` value. Recomputed
baseline context applies each metric's percent and absolute regression floors
and does not override stored status.
The latest status table shows the stored JSON `success` value alongside the
canonical comparison status and reason. Recomputed baseline context applies
each metric's percent and absolute regression floors and does not override
either stored result.

## API

Expand All @@ -108,7 +111,14 @@ V2 records use the same comparison cohort as CI: `workload_id`, `variant_id`,
`benchmark_version`, `recipe_fingerprint`, `hardware_profile_id`, and
`software_profile_id`. `model_id` and `gpu_type` remain display/filter
metadata, so renaming either does not split history. Legacy records still group
by `(model_id, gpu_type)`. Dashboard baselines use the latest five previous
successful, baseline-eligible records in each group. Summary and trend filters
match the latest display metadata after grouping, while the raw records endpoint
continues to filter individual records.
by `(model_id, gpu_type)` under the explicit `Legacy v1` label. Records that
claim or partially use v2 identity without all required fields are labeled
`Invalid v2` and remain scoped by model, GPU, and their available identity
values. Dashboard baselines use the latest five previous successful,
baseline-eligible records in each group.

Summary rows, trend groups, and trend points expose `cohort_kind`,
`result_schema_version`, `baseline_status`, `comparison_status`, and
`comparison_status_reason`. Summary and trend filters match the latest display
metadata after grouping, while the raw records endpoint continues to filter
individual records.
104 changes: 81 additions & 23 deletions apps/performance_dashboard/frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react";

import { fetchSummary, fetchTrends, refreshData } from "./api";
import type { CohortValue, RunSource, SummaryResponse, TrendGroup, TrendPoint } from "./api";
import type { CohortKind, CohortValue, RunSource, SummaryResponse, TrendGroup, TrendPoint } from "./api";

const METRIC_KEYS = ["latency", "throughput", "memory", "text_encoder_time_s", "dit_time_s", "vae_decode_time_s"];
const RUN_SOURCES: Array<{ value: "" | RunSource; label: string }> = [
Expand Down Expand Up @@ -113,6 +113,7 @@ function metricLabel(metricKey: string) {
type CohortFields = {
model_id: string;
gpu_type: string;
cohort_kind: CohortKind;
workload_id: CohortValue;
variant_id: CohortValue;
benchmark_version: CohortValue;
Expand All @@ -121,50 +122,84 @@ type CohortFields = {
software_profile_id: CohortValue;
};

function cohortValue(value: CohortValue) {
function rawCohortValue(value: CohortValue) {
if (value === null || value === undefined || value === "") {
return "legacy";
return "";
}
return String(value);
}

function displayCohortValue(value: CohortValue) {
return rawCohortValue(value) || "missing";
}

function shortCohortValue(value: CohortValue) {
const text = cohortValue(value);
if (text === "legacy" || text.length <= 14) {
return text;
}
return text.slice(0, 12);
const text = displayCohortValue(value);
return text.length <= 14 ? text : text.slice(0, 12);
}

function cohortKey(cohort: CohortFields) {
return [
cohort.cohort_kind,
cohort.model_id,
cohort.gpu_type,
cohortValue(cohort.workload_id),
cohortValue(cohort.variant_id),
cohortValue(cohort.benchmark_version),
cohortValue(cohort.recipe_fingerprint),
cohortValue(cohort.hardware_profile_id),
cohortValue(cohort.software_profile_id)
rawCohortValue(cohort.workload_id),
rawCohortValue(cohort.variant_id),
rawCohortValue(cohort.benchmark_version),
rawCohortValue(cohort.recipe_fingerprint),
rawCohortValue(cohort.hardware_profile_id),
rawCohortValue(cohort.software_profile_id)
].join("|");
}

function cohortTitle(cohort: CohortFields) {
const workload = cohortValue(cohort.workload_id);
const variant = cohortValue(cohort.variant_id);
const version = cohortValue(cohort.benchmark_version);
const versionLabel = version === "legacy" ? version : `v${version}`;
return `${workload} / ${variant} / ${versionLabel}`;
if (cohort.cohort_kind === "legacy_v1") {
return "Legacy v1";
}

const workload = displayCohortValue(cohort.workload_id);
const variant = displayCohortValue(cohort.variant_id);
const version = displayCohortValue(cohort.benchmark_version);
const versionLabel = version === "missing" ? version : "v" + version;
const identity = [workload, variant, versionLabel].join(" / ");
return cohort.cohort_kind === "invalid_v2" ? "Invalid v2: " + identity : identity;
}

function cohortDetail(cohort: CohortFields) {
if (cohort.cohort_kind === "legacy_v1") {
return "";
}
return [
`recipe ${shortCohortValue(cohort.recipe_fingerprint)}`,
"recipe " + shortCohortValue(cohort.recipe_fingerprint),
shortCohortValue(cohort.hardware_profile_id),
shortCohortValue(cohort.software_profile_id)
].join(" | ");
}

function statusLabel(value: string, fallback: string) {
if (!value) {
return fallback;
}
return value
.toLowerCase()
.split("_")
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
}
Comment thread
SolitaryThinker marked this conversation as resolved.

function comparisonStatusLabel(status: string, cohortKind: CohortKind) {
return statusLabel(status, cohortKind === "legacy_v1" ? "Historical" : "Unknown");
}
Comment thread
SolitaryThinker marked this conversation as resolved.

function comparisonStatusClass(status: string, cohortKind: CohortKind) {
const normalized = status.toLowerCase().replaceAll("_", "-");
return normalized
? "comparison-" + normalized
: cohortKind === "legacy_v1"
? "comparison-historical"
: "comparison-unknown";
}
Comment thread
SolitaryThinker marked this conversation as resolved.

function formatMetricValue(metricKey: string, value: number | null | undefined, tooltip = false) {
const definition = METRIC_DEFINITIONS[metricKey];
if (!definition) {
Expand Down Expand Up @@ -273,7 +308,9 @@ function TrendChart({ group, metricKey }: { group: TrendGroup; metricKey: string
{chartPoints.map((point) => {
const pointLabel = `${metricLabel(metricKey)} ${formatMetricValue(metricKey, point.value, true)} at ${formatTime(
point.point.timestamp
)}, commit ${shortSha(point.point.commit_sha)}, ${runSourceLabel(point.point.run_source)}`;
)}, commit ${shortSha(point.point.commit_sha)}, ${runSourceLabel(
point.point.run_source
)}, comparison ${comparisonStatusLabel(point.point.comparison_status, point.point.cohort_kind)}`;
return (
<g
key={`${point.plotIndex}-${point.value}-${point.point.commit_sha ?? ""}`}
Expand Down Expand Up @@ -307,6 +344,9 @@ function TrendChart({ group, metricKey }: { group: TrendGroup; metricKey: string
{metric?.secondary ? <span>{metric.secondary(activePoint.value)}</span> : null}
<span>{shortSha(activePoint.point.commit_sha)}</span>
<span>{runSourceLabel(activePoint.point.run_source)}</span>
<span>
{comparisonStatusLabel(activePoint.point.comparison_status, activePoint.point.cohort_kind)}
</span>
</div>
) : null}
<div className="point-tooltip" aria-live="polite">
Expand All @@ -318,6 +358,13 @@ function TrendChart({ group, metricKey }: { group: TrendGroup; metricKey: string
<span>Commit {shortSha(selectedPoint.point.commit_sha)}</span>
<span>{runSourceLabel(selectedPoint.point.run_source)}</span>
<span>{selectedPoint.point.success ? "Stored status: pass" : "Stored status: fail"}</span>
<span>
Comparison: {comparisonStatusLabel(selectedPoint.point.comparison_status, selectedPoint.point.cohort_kind)}
</span>
<span>Baseline status: {statusLabel(selectedPoint.point.baseline_status, "Unavailable")}</span>
{selectedPoint.point.comparison_status_reason ? (
<span className="tooltip-wide">{selectedPoint.point.comparison_status_reason}</span>
) : null}
<span>{selectedPoint.point.baseline_eligible ? "Baseline eligible" : "Not baseline eligible"}</span>
{selectedPoint.point.pr_number ? <span>PR #{selectedPoint.point.pr_number}</span> : null}
{selectedPoint.point.branch ? <span>Branch {selectedPoint.point.branch}</span> : null}
Expand Down Expand Up @@ -486,6 +533,7 @@ export default function App() {
<table>
<thead>
<tr>
<th>Comparison</th>
<th>Stored Status</th>
<th>Recomputed</th>
<th>Model</th>
Expand All @@ -506,6 +554,16 @@ export default function App() {
<tbody>
{latestRows.map((row) => (
<tr key={cohortKey(row)}>
<td>
<div className="status-cell">
<span className={`badge ${comparisonStatusClass(row.comparison_status, row.cohort_kind)}`}>
{comparisonStatusLabel(row.comparison_status, row.cohort_kind)}
</span>
{row.comparison_status_reason ? (
<span className="status-reason">{row.comparison_status_reason}</span>
) : null}
</div>
</td>
<td>
<span className={`badge ${row.status}`}>{row.status}</span>
</td>
Expand All @@ -519,7 +577,7 @@ export default function App() {
<td>
<div className="cohort-cell">
<strong>{cohortTitle(row)}</strong>
<span>{cohortDetail(row)}</span>
{cohortDetail(row) ? <span>{cohortDetail(row)}</span> : null}
</div>
</td>
<td>{shortSha(row.commit_sha)}</td>
Expand Down Expand Up @@ -566,7 +624,7 @@ export default function App() {
<p>
{group.model_id} | {group.gpu_type}
<span>{cohortTitle(group)}</span>
<span>{cohortDetail(group)}</span>
{cohortDetail(group) ? <span>{cohortDetail(group)}</span> : null}
</p>
</div>
<TrendChart group={group} metricKey={metricKey} />
Expand Down
15 changes: 12 additions & 3 deletions apps/performance_dashboard/frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ export type ComparisonCohort = {
hardware_profile_id: CohortValue;
software_profile_id: CohortValue;
};
export type CohortKind = "v2" | "legacy_v1" | "invalid_v2";

export type DashboardRecordMetadata = {
cohort_kind: CohortKind;
result_schema_version: CohortValue;
baseline_status: string;
comparison_status: string;
comparison_status_reason: string;
};

export type SummaryRow = {
model_id: string;
Expand All @@ -45,7 +54,7 @@ export type SummaryRow = {
build_id: string;
job_id: string;
metrics: Record<string, MetricValue>;
} & ComparisonCohort;
} & ComparisonCohort & DashboardRecordMetadata;

export type RunSource = "pr" | "local" | "scheduled_main" | "unknown";

Expand Down Expand Up @@ -79,13 +88,13 @@ export type TrendPoint = {
build_id: string;
job_id: string;
metrics: Record<string, number | null>;
} & ComparisonCohort;
} & ComparisonCohort & DashboardRecordMetadata;

export type TrendGroup = {
model_id: string;
gpu_type: string;
points: TrendPoint[];
} & ComparisonCohort;
} & ComparisonCohort & DashboardRecordMetadata;

export type TrendsResponse = {
groups: TrendGroup[];
Expand Down
48 changes: 45 additions & 3 deletions apps/performance_dashboard/frontend/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ h3 {

table {
width: 100%;
min-width: 1260px;
min-width: 1500px;
border-collapse: collapse;
}

Expand Down Expand Up @@ -233,6 +233,19 @@ td {
font-size: 0.72rem;
}

.status-cell {
display: grid;
gap: 4px;
max-width: 280px;
}

.status-reason {
color: #607080;
font-size: 0.72rem;
line-height: 1.35;
white-space: normal;
}

.badge {
display: inline-flex;
align-items: center;
Expand All @@ -243,18 +256,43 @@ td {
font-size: 0.75rem;
font-weight: 800;
text-transform: uppercase;
white-space: nowrap;
}

.badge.pass {
.badge.pass,
.badge.comparison-pass {
color: #166534;
background: #dcfce7;
}

.badge.fail {
.badge.fail,
.badge.comparison-regression,
.badge.comparison-infra-error {
color: #991b1b;
background: #fee2e2;
}

.badge.comparison-calibration-needed {
color: #92400e;
background: #fef3c7;
}

.badge.comparison-recipe-mismatch {
color: #1d4ed8;
background: #dbeafe;
}

.badge.comparison-quality-blocked {
color: #6b3e00;
background: #ffedd5;
}

.badge.comparison-historical,
.badge.comparison-unknown {
color: #475569;
background: #e2e8f0;
}

.badge.muted {
opacity: 0.78;
}
Expand Down Expand Up @@ -403,6 +441,10 @@ td {
font-size: 0.9rem;
}

.point-tooltip .tooltip-wide {
grid-column: 1 / -1;
}

.point-tooltip a {
color: #0f6b8f;
font-weight: 700;
Expand Down
11 changes: 10 additions & 1 deletion docs/contributing/performance_benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,13 @@ main/full-suite uploads and remain eligible for rolling baselines.
Current `perf_*.json` artifacts that lack the v2 comparison identity are
normalized for reporting but skip rolling-baseline comparison and are not marked
baseline eligible.
Dashboard reports keep historical v1 records readable by grouping them under
the explicit `Legacy v1` label by `(model_id, gpu_type)`. Complete v2 cohorts
remain separate and use the six-field comparison identity. Records that claim
or partially use v2 identity without all required fields are labeled
`Invalid v2` and remain scoped by their display metadata and available identity
fields. Stored comparison status, comparison reason, and baseline status are
shown when present; old records remain readable when those fields are absent.

New v2 records compare only against the same `workload_id`, `variant_id`,
`benchmark_version`, `recipe_fingerprint`, `hardware_profile_id`, and
Expand Down Expand Up @@ -487,7 +494,9 @@ When the rolling-baseline phase runs, it emits:
per-benchmark row with current vs. baseline values for latency, throughput,
memory, text encoder time, DiT time, and VAE decode time.
* **Plotly dashboard** — `dashboard_<sha>_<ts>.html` showing time-series for
each metric grouped by comparison cohort.
each metric grouped by comparison cohort. Chart titles distinguish
`Legacy v1` and incomplete `Invalid v2` records, and point hover data includes
stored comparison and baseline status metadata.
* **Normalized records** — `normalized_perf_*.json`, one per benchmark.
Useful as input to the
[`reseed-performance-baseline`](https://github.com/hao-ai-lab/FastVideo/blob/main/.agents/skills/reseed-performance-baseline/SKILL.md)
Expand Down
Loading
Loading