Compare commits

...
Author SHA1 Message Date
SolitaryThinker 30ef682227 [ci]: adopt per-parallelism-config baseline cohorts for wan-t2v (author decision)
Flip the wan benchmark identity from workload wan-t2v-1.3b / variant
canonical / benchmark_version 1 to workload wan-t2v / variant 1.3b-sp2 /
benchmark_version 2, adopting the contributor's per-parallelism-config
cohort labels and matching the #1551 fixtures. This deliberately opens a
new comparison cohort; the baseline reseed follows post-merge.
config_schema_version stays 2 as required by the #1544 validator.
2026-07-05 12:48:08 -07:00
SolitaryThinker ee34595f53 [bugfix]: address verify-pass findings on the 1546 fix branch
- recipe_fingerprint also excludes attention.resolved_backend (a silent
  runtime fallback must fail the gate in its cohort, not open a fresh
  ungated one); requested_backend stays in the hash; regression test
  extended to both runtime-resolved values.
- Legacy v1 configs no longer crash after the GPU measurement: identity
  fields are emitted only for config_schema_version=2 configs, and the
  comparator falls back to the legacy (model_id, gpu_type) cohort when a
  record carries no identity fields (partial identity still raises).
- 'recipe' removed from V2_OPTIONAL_METADATA_FIELDS: the generated recipe
  owns the key now; declaring it in a config fails validation loudly
  instead of being silently overwritten.
- Docs: stale 'until those land' paragraph reconciled with what this
  branch actually lands; baseline_status documented.
2026-07-05 12:19:54 -07:00
SolitaryThinker 90bdd90b38 [docs]: align example records with the merged identity taxonomy 2026-07-05 12:19:54 -07:00
SolitaryThinker 527cf68742 [bugfix]: pin recipe_fingerprint to declared inputs and make cohort initialization loud
Rebase of #1546 onto main resolved the identity taxonomy in favor of the
merged #1544 values (workload_id=wan-t2v-1.3b, variant_id=canonical,
benchmark_version=1, config_schema_version=2). On top:
- recipe_fingerprint() no longer hashes the runtime-resolved HF snapshot
  revision (kept in the recipe as audit metadata) — upstream repo commits
  with unchanged weights no longer churn the cohort and reset baselines.
- The no-baseline branch now emits a loud warning block and a
  machine-readable baseline_status=initialized_new_cohort field instead of
  an indistinguishable success, so cohort shifts never silently disable
  regression gating.
- Regression test: resolved revision must not change the fingerprint.
2026-07-05 12:19:54 -07:00
Mac Lee df8106ddbd [ci]: align remaining performance cohort consumers 2026-07-05 12:19:36 -07:00
Mac Lee a7e2a75caf [ci]: complete performance cohort identity 2026-07-05 12:19:08 -07:00
Mac Lee cf5d2209c9 [ci]: tighten performance cohort identity 2026-07-05 12:19:08 -07:00
Mac Lee 663dfb2f37 [ci]: add performance fingerprint cohorts 2026-07-05 12:19:08 -07:00
16 changed files with 1830 additions and 57 deletions
@@ -1,9 +1,9 @@
{
"benchmark_id": "wan-t2v-1.3b-2gpu",
"config_schema_version": 2,
"workload_id": "wan-t2v-1.3b",
"variant_id": "canonical",
"benchmark_version": 1,
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
"description": "Wan2.1 T2V 1.3B inference performance",
"model": {
"model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
@@ -1,6 +1,7 @@
import { useEffect, useMemo, useState } from "react";
import { fetchSummary, fetchTrends, refreshData, RunSource, SummaryResponse, TrendGroup, TrendPoint } from "./api";
import { fetchSummary, fetchTrends, refreshData } from "./api";
import type { 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 }> = [
@@ -109,6 +110,61 @@ function metricLabel(metricKey: string) {
return METRIC_DEFINITIONS[metricKey]?.label ?? metricKey;
}
type CohortFields = {
model_id: string;
gpu_type: string;
workload_id: CohortValue;
variant_id: CohortValue;
benchmark_version: CohortValue;
recipe_fingerprint: CohortValue;
hardware_profile_id: CohortValue;
software_profile_id: CohortValue;
};
function cohortValue(value: CohortValue) {
if (value === null || value === undefined || value === "") {
return "legacy";
}
return String(value);
}
function shortCohortValue(value: CohortValue) {
const text = cohortValue(value);
if (text === "legacy" || text.length <= 14) {
return text;
}
return text.slice(0, 12);
}
function cohortKey(cohort: CohortFields) {
return [
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)
].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}`;
}
function cohortDetail(cohort: CohortFields) {
return [
`recipe ${shortCohortValue(cohort.recipe_fingerprint)}`,
shortCohortValue(cohort.hardware_profile_id),
shortCohortValue(cohort.software_profile_id)
].join(" | ");
}
function formatMetricValue(metricKey: string, value: number | null | undefined, tooltip = false) {
const definition = METRIC_DEFINITIONS[metricKey];
if (!definition) {
@@ -171,7 +227,9 @@ function TrendChart({ group, metricKey }: { group: TrendGroup; metricKey: string
top: `${(activePoint.y / height) * 100}%`
}
: undefined;
const ariaLabel = `${metricLabel(metricKey)} trend for ${group.model_id} on ${group.gpu_type}`;
const ariaLabel = `${metricLabel(metricKey)} trend for ${group.model_id} on ${group.gpu_type}, ${cohortTitle(
group
)}`;
return (
<div className="chart-shell">
@@ -419,7 +477,7 @@ export default function App() {
<section className="panel">
<div className="panel-header">
<h2>Latest Status</h2>
<span>{latestRows.length} model/GPU groups</span>
<span>{latestRows.length} comparison cohorts</span>
</div>
{latestRows.length === 0 ? (
<div className="empty">No records match the selected filters.</div>
@@ -432,6 +490,7 @@ export default function App() {
<th>Recomputed</th>
<th>Model</th>
<th>GPU</th>
<th>Cohort</th>
<th>Commit</th>
<th>Source</th>
<th>Baseline</th>
@@ -446,7 +505,7 @@ export default function App() {
</thead>
<tbody>
{latestRows.map((row) => (
<tr key={`${row.model_id}-${row.gpu_type}`}>
<tr key={cohortKey(row)}>
<td>
<span className={`badge ${row.status}`}>{row.status}</span>
</td>
@@ -457,6 +516,12 @@ export default function App() {
</td>
<td>{row.model_id}</td>
<td>{row.gpu_type}</td>
<td>
<div className="cohort-cell">
<strong>{cohortTitle(row)}</strong>
<span>{cohortDetail(row)}</span>
</div>
</td>
<td>{shortSha(row.commit_sha)}</td>
<td>
<span className={`source-badge source-${row.run_source}`}>{runSourceLabel(row.run_source)}</span>
@@ -495,11 +560,13 @@ export default function App() {
) : (
trends.map((group) =>
METRIC_KEYS.map((metricKey) => (
<article className="trend-card" key={`${group.model_id}-${group.gpu_type}-${metricKey}`}>
<article className="trend-card" key={`${cohortKey(group)}-${metricKey}`}>
<div>
<h3>{metricLabel(metricKey)}</h3>
<p>
{group.model_id} | {group.gpu_type}
<span>{cohortTitle(group)}</span>
<span>{cohortDetail(group)}</span>
</p>
</div>
<TrendChart group={group} metricKey={metricKey} />
+14 -3
View File
@@ -13,6 +13,17 @@ export type MetricValue = {
precision: number;
};
export type CohortValue = string | number | null;
export type ComparisonCohort = {
workload_id: CohortValue;
variant_id: CohortValue;
benchmark_version: CohortValue;
recipe_fingerprint: CohortValue;
hardware_profile_id: CohortValue;
software_profile_id: CohortValue;
};
export type SummaryRow = {
model_id: string;
gpu_type: string;
@@ -34,7 +45,7 @@ export type SummaryRow = {
build_id: string;
job_id: string;
metrics: Record<string, MetricValue>;
};
} & ComparisonCohort;
export type RunSource = "pr" | "local" | "scheduled_main" | "unknown";
@@ -68,13 +79,13 @@ export type TrendPoint = {
build_id: string;
job_id: string;
metrics: Record<string, number | null>;
};
} & ComparisonCohort;
export type TrendGroup = {
model_id: string;
gpu_type: string;
points: TrendPoint[];
};
} & ComparisonCohort;
export type TrendsResponse = {
groups: TrendGroup[];
@@ -149,6 +149,11 @@ h3 {
font-size: 0.82rem;
}
.trend-card p {
display: grid;
gap: 2px;
}
.stat strong {
display: block;
margin-top: 8px;
@@ -186,7 +191,7 @@ h3 {
table {
width: 100%;
min-width: 1120px;
min-width: 1260px;
border-collapse: collapse;
}
@@ -209,6 +214,25 @@ td {
font-size: 0.9rem;
}
.cohort-cell {
display: grid;
gap: 2px;
}
.cohort-cell strong,
.trend-card p span {
color: #1b2836;
font-size: 0.78rem;
font-weight: 700;
}
.cohort-cell span,
.trend-card p span + span {
color: #607080;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: 0.72rem;
}
.badge {
display: inline-flex;
align-items: center;
+77 -16
View File
@@ -200,9 +200,9 @@ configs and remain loadable. New or migrated configs should use
{
"benchmark_id": "wan-t2v-1.3b-2gpu",
"config_schema_version": 2,
"workload_id": "wan-t2v-1.3b",
"variant_id": "canonical",
"benchmark_version": 1
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2
}
```
@@ -213,21 +213,25 @@ metadata that make the measured workload explicit:
| Field | Purpose |
|---|---|
| `workload_id` | Stable benchmark family, such as `wan-t2v-1.3b`. |
| `variant_id` | Intentional recipe family, such as `canonical`. |
| `workload_id` | Stable benchmark family, such as `wan-t2v`. |
| `variant_id` | Intentional recipe family, including model size and parallelism config, such as `1.3b-sp2`. |
| `benchmark_version` | Version of the measurement protocol and comparison policy. |
If a config declares `config_schema_version: 2`, loading fails clearly when any
required v2 identity field is missing. If v2 identity or metadata fields are
added without `config_schema_version: 2`, loading also fails so partial
migrations do not silently run as v1 configs. Optional v2 metadata fields
reserved for follow-up work, such as `recipe`, `metric_threshold_policy`, and
`quality_metadata`, must be JSON objects when present.
reserved for follow-up work, such as `metric_threshold_policy` and
`quality_metadata`, must be JSON objects when present. (`recipe` is emitted
by the harness and is not config-declarable.)
Recipe fingerprinting, hardware/software profile IDs, exact-identity
comparison, metric-specific threshold policy behavior, promoted baselines, and
dashboard regrouping are separate follow-up changes. Until those land, rolling
baseline comparison remains keyed by `(model_id, gpu_type)`.
comparison, and dashboard cohort grouping land with this change: v2 records
compare only within their identity cohort, and a record that opens a NEW
cohort is marked `baseline_status: "initialized_new_cohort"` (regression
gating starts once that cohort accumulates history). Legacy v1 configs keep
the `(model_id, gpu_type)` rolling-baseline comparison. Metric-specific
threshold policies and promoted baselines remain separate follow-ups.
### Raw record (`results/perf_*.json`)
@@ -237,9 +241,9 @@ Written by `test_inference_performance.py`. One file per benchmark run.
{
"benchmark_id": "wan-t2v-1.3b-2gpu",
"config_schema_version": 2,
"workload_id": "wan-t2v-1.3b",
"variant_id": "canonical",
"benchmark_version": 1,
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
"model_short_name": "Wan2.1-T2V-1.3B-Diffusers",
"device": "NVIDIA L40S",
"num_gpus": 2,
@@ -269,7 +273,43 @@ Written by `test_inference_performance.py`. One file per benchmark run.
"timestamp": "2026-05-08T22:00:00+00:00",
"text_encoder_time_s": 2.141,
"dit_time_s": 8.437,
"vae_decode_time_s": 3.208
"vae_decode_time_s": 3.208,
"recipe": {
"recipe_schema_version": 1,
"benchmark": {
"benchmark_id": "wan-t2v-1.3b-2gpu",
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2
},
"model": { "model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers" },
"init_kwargs": { "num_gpus": 2, "sp_size": 2, "tp_size": 1 },
"generation_kwargs": { "height": 480, "width": 832, "num_frames": 45 },
"inputs": { "prompt_count": 1, "prompt_sha256": ["<measured-prompt-sha256>"] },
"attention": { "requested_backend": "FLASH_ATTN", "resolved_backend": "FLASH_ATTN" }
},
"recipe_fingerprint": "<sha256>",
"hardware_profile": {
"device_type": "cuda",
"gpu_count": 2,
"gpus": [{ "name": "NVIDIA L40S", "memory_gb": 48, "compute_capability": "8.9" }],
"interconnect": "none_or_partial"
},
"hardware_profile_id": "hw-<sha256-prefix>",
"software_profile": {
"python": "3.12",
"pytorch": "2.12",
"cuda": "13.0",
"packages": {
"fastvideo_kernel": "0.3.2",
"flashinfer": "0.2.11",
"nvidia_cutlass_dsl": "4.5.0",
"triton": "3.4.1"
}
},
"software_profile_id": "sw-<sha256-prefix>",
"environment_metadata": { "env": { "IMAGE_VERSION": "py3.12-cuda13.0.0" } },
"environment_fingerprint": "env-<sha256-prefix>"
}
```
@@ -281,6 +321,9 @@ result, used as the rolling-baseline source of truth.
```jsonc
{
"model_id": "wan-t2v-1.3b-2gpu",
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
"timestamp": "2026-05-08T22:00:00+00:00",
"commit_sha": "<full sha>",
"gpu_type": "NVIDIA L40S",
@@ -297,6 +340,10 @@ result, used as the rolling-baseline source of truth.
"gated": true
}
},
"recipe_fingerprint": "<sha256>",
"hardware_profile_id": "hw-<sha256-prefix>",
"software_profile_id": "sw-<sha256-prefix>",
"environment_fingerprint": "env-<sha256-prefix>",
"success": true
}
```
@@ -309,6 +356,18 @@ dashboard lists skipped plots for metric series that have no non-null values.
Records missing both `run_source` and `baseline_eligible` are treated as legacy
successful main/full-suite uploads and remain eligible for rolling baselines.
New records compare only against the same `model_id`, `gpu_type`,
`workload_id`, `variant_id`, `benchmark_version`, `recipe_fingerprint`,
`hardware_profile_id`, and `software_profile_id` cohort.
`environment_metadata` and `environment_fingerprint` are audit data and are not
part of the comparison key.
The recipe prompt digests describe the prompts actually measured by the
benchmark run; extra configured prompts are ignored unless the benchmark runner
executes them.
Software profile package cohorts keep exact versions for relevant
attention/kernel packages, including FastVideo kernels, FlashAttention,
FlashInfer, Cutlass DSL, SageAttention, Triton, and xFormers when installed.
## Environment variable reference
| Variable | Default | Used by | Purpose |
@@ -363,7 +422,7 @@ When the rolling-baseline phase runs, it emits:
"benchmark_id": "<unique-id>",
"config_schema_version": 2,
"workload_id": "<stable-workload-id>",
"variant_id": "canonical",
"variant_id": "<variant, e.g. 1.3b-sp2>",
"benchmark_version": 1,
"model": { "model_path": "...", "model_short_name": "..." },
"init_kwargs": { "num_gpus": 1, ... },
@@ -390,7 +449,9 @@ When the rolling-baseline phase runs, it emits:
Legacy v1 configs without `config_schema_version` still load, but should not
gain v2 identity or metadata fields until they are migrated to
`config_schema_version: 2`.
`config_schema_version: 2`. For v2 configs, `workload_id`, `variant_id`,
and `benchmark_version` are part of the comparison key; benchmark runs
fail if any of these identity fields are missing.
2. The pytest test auto-discovers all configs — no test code needed. CI
picks it up on the next `/test performance` run.
+28 -1
View File
@@ -268,16 +268,31 @@ def load_records_for_model(
model_id: str,
gpu_type: str | None = None,
*,
workload_id: str | None = None,
variant_id: str | None = None,
benchmark_version: str | None = None,
recipe_fingerprint: str | None = None,
hardware_profile_id: str | None = None,
software_profile_id: str | None = None,
last_n: int | None = None,
successful_only: bool = True,
baseline_eligible_only: bool = False,
) -> list[dict[str, Any]]:
"""Return records for a specific *model_id*, optionally filtered by GPU.
"""Return records for a specific *model_id*, optionally filtered by cohort.
Args:
local_dir: Root directory previously populated by :func:`sync_from_hf`.
model_id: Matches the ``model_id`` field inside each JSON record.
gpu_type: When set, only records whose ``gpu_type`` matches are returned.
workload_id: When set, only records from the same workload are returned.
variant_id: When set, only records from the same workload variant are returned.
benchmark_version: When set, only records from the same benchmark version are returned.
recipe_fingerprint: When set, only records from the same benchmark
recipe are returned.
hardware_profile_id: When set, only records from the same hardware
cohort are returned.
software_profile_id: When set, only records from the same software
cohort are returned.
last_n: When set, return only the most recent *n* records (after all
other filters). Useful for sliding-window baseline calculations.
successful_only: Passed through to :func:`load_records`.
@@ -299,6 +314,18 @@ def load_records_for_model(
if gpu_type is not None:
records = [r for r in records if r.get("gpu_type") == gpu_type]
identity_filters = {
"workload_id": workload_id,
"variant_id": variant_id,
"benchmark_version": benchmark_version,
"recipe_fingerprint": recipe_fingerprint,
"hardware_profile_id": hardware_profile_id,
"software_profile_id": software_profile_id,
}
for key, expected in identity_filters.items():
if expected is not None:
records = [r for r in records if str(r.get(key)) == str(expected)]
if last_n is not None:
records = records[-last_n:]
+52 -9
View File
@@ -17,6 +17,15 @@ from fastvideo.performance.hf_store import is_baseline_eligible_record, safe_flo
from fastvideo.performance.metric_policy import regression_delta, resolve_metric_policies
Record = dict[str, Any]
CohortKey = tuple[str, str, str, str, str, str, str, str]
COMPARISON_COHORT_KEYS = (
"workload_id",
"variant_id",
"benchmark_version",
"recipe_fingerprint",
"hardware_profile_id",
"software_profile_id",
)
def parse_timestamp(value: Any) -> datetime | None:
@@ -77,15 +86,44 @@ def record_metadata(record: Record) -> Record:
}
def group_by_model_gpu(records: list[Record]) -> dict[tuple[str, str], list[Record]]:
groups: dict[tuple[str, str], list[Record]] = defaultdict(list)
def record_comparison_metadata(record: Record) -> Record:
return {key: record.get(key) or "" for key in COMPARISON_COHORT_KEYS}
def comparison_cohort_key(record: Record) -> CohortKey:
return (
str(record.get("model_id") or "unknown"),
str(record.get("gpu_type") or "unknown"),
str(record.get("workload_id") or ""),
str(record.get("variant_id") or ""),
str(record.get("benchmark_version") or ""),
str(record.get("recipe_fingerprint") or ""),
str(record.get("hardware_profile_id") or ""),
str(record.get("software_profile_id") or ""),
)
def group_by_comparison_cohort(records: list[Record]) -> dict[CohortKey, list[Record]]:
groups: dict[CohortKey, list[Record]] = defaultdict(list)
for record in records:
model_id = str(record.get("model_id") or "unknown")
gpu_type = str(record.get("gpu_type") or "unknown")
groups[(model_id, gpu_type)].append(record)
groups[comparison_cohort_key(record)].append(record)
return {key: sorted(value, key=record_sort_key) for key, value in groups.items()}
def comparison_sort_key(record: Record) -> CohortKey:
return comparison_cohort_key(record)
def latest_row_sort_key(row: Record) -> tuple[Any, ...]:
return (row["status"] != "fail", *comparison_sort_key(row))
def group_identity(key: CohortKey) -> tuple[str, str]:
model_id = key[0]
gpu_type = key[1]
return model_id, gpu_type
def baseline_value(records: list[Record], metric_key: str) -> float | None:
values = [safe_float(record.get(metric_key)) for record in records]
values = [value for value in values if value is not None]
@@ -99,7 +137,8 @@ def build_latest_summary(records: list[Record],
baseline_window: int = 5,
run_source: str | None = None) -> list[Record]:
rows: list[Record] = []
for (model_id, gpu_type), group in group_by_model_gpu(records).items():
for key, group in group_by_comparison_cohort(records).items():
model_id, gpu_type = group_identity(key)
latest_candidates = group
if run_source:
latest_candidates = [record for record in group if record_run_source(record) == run_source]
@@ -156,6 +195,7 @@ def build_latest_summary(records: list[Record],
"timestamp": latest.get("timestamp"),
"commit_sha": latest.get("commit_sha"),
**record_metadata(latest),
**record_comparison_metadata(latest),
"success": success,
"baseline_n": len(baseline_records),
"worst_regression_pct": worst_regression,
@@ -166,12 +206,13 @@ def build_latest_summary(records: list[Record],
"metrics": metrics,
})
return sorted(rows, key=lambda row: (row["status"] != "fail", row["model_id"], row["gpu_type"]))
return sorted(rows, key=latest_row_sort_key)
def build_trends(records: list[Record]) -> list[Record]:
trends: list[Record] = []
for (model_id, gpu_type), group in group_by_model_gpu(records).items():
for key, group in group_by_comparison_cohort(records).items():
model_id, gpu_type = group_identity(key)
points = []
for record in group:
metric_policies = resolve_metric_policies(record.get("regression_thresholds"))
@@ -179,6 +220,7 @@ def build_trends(records: list[Record]) -> list[Record]:
"timestamp": record.get("timestamp"),
"commit_sha": record.get("commit_sha"),
**record_metadata(record),
**record_comparison_metadata(record),
"success": bool(record.get("success", True)),
"metrics": {
policy.key: safe_float(record.get(policy.key))
@@ -189,6 +231,7 @@ def build_trends(records: list[Record]) -> list[Record]:
trends.append({
"model_id": model_id,
"gpu_type": gpu_type,
**record_comparison_metadata(group[-1]),
"points": points,
})
return sorted(trends, key=lambda trend: (trend["model_id"], trend["gpu_type"]))
return sorted(trends, key=comparison_sort_key)
@@ -5,7 +5,8 @@ This script:
1) reads current benchmark results from fastvideo/tests/performance/results,
2) syncs the canonical baseline from the configured HF dataset repo,
3) compares each current record against the median of up to 5 prior
baseline-eligible successful records (filtered by gpu_type),
baseline-eligible successful records in the same workload/variant/version,
GPU, recipe, hardware, and software cohort,
4) writes normalized records back to the HF dataset repo according to
PERF_UPLOAD_POLICY,
5) exits non-zero if any gated metric exceeds both its percent and absolute
@@ -64,6 +65,27 @@ PERF_REPORTS_DIR = os.environ.get("PERF_REPORTS_DIR", "/root/data/perf_reports")
UPLOAD_POLICY = os.environ.get("PERF_UPLOAD_POLICY", "never").strip().lower()
VALID_UPLOAD_POLICIES = {"never", "pass", "always"}
VALID_RUN_SOURCES = {"pr", "local", "scheduled_main", "unknown"}
IDENTITY_KEYS = (
"workload_id",
"variant_id",
"benchmark_version",
"recipe",
"recipe_fingerprint",
"hardware_profile",
"hardware_profile_id",
"software_profile",
"software_profile_id",
"environment_metadata",
"environment_fingerprint",
)
COMPARISON_IDENTITY_KEYS = (
"workload_id",
"variant_id",
"benchmark_version",
"recipe_fingerprint",
"hardware_profile_id",
"software_profile_id",
)
def _should_persist_tracking() -> bool:
@@ -136,6 +158,46 @@ def _record_metadata(run_source: str, result: dict[str, Any]) -> dict[str, Any]:
}
def _identity_metadata(result: dict[str, Any]) -> dict[str, Any]:
metadata = {
key: result[key]
for key in IDENTITY_KEYS
if key in result and result[key] is not None
}
recipe = result.get("recipe")
benchmark = recipe.get("benchmark") if isinstance(recipe, dict) else None
if isinstance(benchmark, dict):
for key in ("workload_id", "variant_id", "benchmark_version"):
if key not in metadata and benchmark.get(key) is not None:
metadata[key] = benchmark[key]
return metadata
def _comparison_identity_filters(record: dict[str, Any]) -> dict[str, str]:
missing = [
key for key in COMPARISON_IDENTITY_KEYS
if key not in record or record[key] is None or (isinstance(record[key], str) and not record[key].strip())
]
if len(missing) == len(COMPARISON_IDENTITY_KEYS):
# Legacy v1 record: no identity fields at all. Keep the pre-v2
# (model_id, gpu_type) rolling-baseline comparison instead of
# crashing — v1 configs are documented as loadable.
return {}
if missing:
raise ValueError("Performance record missing required comparison identity fields: " + ", ".join(missing))
return {
key: str(record[key])
for key in COMPARISON_IDENTITY_KEYS
}
def _format_identity_filters(filters: dict[str, str]) -> str:
if not filters:
return ""
compact = ", ".join(f"{key}={value}" for key, value in filters.items())
return f" ({compact})"
def _load_current_results() -> list[dict[str, Any]]:
pattern = os.path.join(RESULTS_DIR, "perf_*.json")
records: list[dict[str, Any]] = []
@@ -169,7 +231,7 @@ def normalize_performance_result(result: dict[str, Any]) -> dict[str, Any]:
vae_decode_time = safe_float(result.get("vae_decode_time_s"))
metric_policies = resolve_metric_policies(result.get("regression_thresholds"))
return {
record = {
"model_id": model_id,
"timestamp": timestamp,
"commit_sha": commit_sha,
@@ -184,6 +246,8 @@ def normalize_performance_result(result: dict[str, Any]) -> dict[str, Any]:
"success": True,
**_record_metadata(_detect_run_source(), result),
}
record.update(_identity_metadata(result))
return record
def _normalize_record(result: dict[str, Any]) -> dict[str, Any]:
@@ -422,17 +486,31 @@ def main() -> int:
TRACKING_ROOT,
record["model_id"],
record["gpu_type"],
**_comparison_identity_filters(record),
last_n=5,
successful_only=True,
baseline_eligible_only=True,
)
if not baseline_records:
print(f"No baseline for {record['model_id']} on "
f"{record['gpu_type']}. Initializing...")
# A brand-new cohort has NO regression gating until history
# accumulates — make that loud and machine-readable instead of
# an indistinguishable pass, so a cohort shift (intended or
# accidental, e.g. an identity-field change) never silently
# blinds the comparison.
record["baseline_status"] = "initialized_new_cohort"
print("=" * 72)
print(f"WARNING: NO BASELINE — initializing a NEW cohort for "
f"{record['model_id']} on {record['gpu_type']}"
f"{_format_identity_filters(_comparison_identity_filters(record))}")
print("Regression gating is INACTIVE for this cohort until "
"baseline history accumulates. If this cohort shift is "
"unexpected, check the identity fields above.")
print("=" * 72)
failures: list[str] = []
record["success"] = True
else:
record["baseline_status"] = "compared"
failures = _check_regressions(record, baseline_records, metric_policies)
if static_threshold_failed:
failures.append(f"{record['model_id']} fixed-threshold phase failed "
+81 -12
View File
@@ -29,15 +29,76 @@ METRICS = (
"dit_time_s",
"vae_decode_time_s",
)
COMPARISON_COHORT_KEYS = (
"workload_id",
"variant_id",
"benchmark_version",
"recipe_fingerprint",
"hardware_profile_id",
"software_profile_id",
)
GROUP_KEYS = ("model_id", "gpu_type", *COMPARISON_COHORT_KEYS)
def _cohort_value(value: object) -> str:
if value is None:
return ""
try:
if pd.isna(value):
return ""
except (TypeError, ValueError):
pass
return str(value)
def _display_value(value: object) -> str:
return _cohort_value(value) or "legacy"
def _short_value(value: object) -> str:
text = _display_value(value)
if text == "legacy" or len(text) <= 14:
return text
return text[:12]
def _cohort_title(group_key: tuple[object, ...]) -> str:
workload = _display_value(group_key[2])
variant = _display_value(group_key[3])
version = _display_value(group_key[4])
version_label = version if version == "legacy" else f"v{version}"
return f"{workload} / {variant} / {version_label}"
def _cohort_detail(group_key: tuple[object, ...]) -> str:
return " | ".join((
f"recipe {_short_value(group_key[5])}",
_short_value(group_key[6]),
_short_value(group_key[7]),
))
def _group_record(group_key: tuple[object, ...]) -> dict[str, str]:
return {
key: _cohort_value(value)
for key, value in zip(GROUP_KEYS, group_key)
}
def _dashboard_frame(df: pd.DataFrame) -> pd.DataFrame:
dashboard_df = df.copy()
for key in GROUP_KEYS:
if key not in dashboard_df.columns:
dashboard_df[key] = ""
dashboard_df[key] = dashboard_df[key].map(_cohort_value)
return dashboard_df
# -----------------------------
# 1. Grouping
# -----------------------------
def group_data(df: pd.DataFrame):
# Group only by model+GPU so each group produces a time-series line.
# config_id (commit SHA) is carried as a column for hover/color use.
keys = ["model_id", "gpu_type"]
return df.groupby(keys, dropna=False)
# Group by the same comparison cohort used by baseline gating.
return _dashboard_frame(df).groupby(list(GROUP_KEYS), dropna=False)
# -----------------------------
# 2. Plot builder
@@ -46,15 +107,20 @@ def build_plots(df: pd.DataFrame) -> tuple[list, list[dict[str, object]]]:
figs = []
skipped_metrics: list[dict[str, object]] = []
for (model_id, gpu_type), g in group_data(df):
for group_key, g in group_data(df):
model_id, gpu_type = group_key[:2]
group_record = _group_record(group_key)
cohort_title = _cohort_title(group_key)
cohort_detail = _cohort_detail(group_key)
g = g.sort_values("timestamp")
# One chart per metric so the y-axes aren't on wildly different scales
for metric in METRICS:
if metric not in g.columns:
skipped_metrics.append({
"model_id": model_id,
"gpu_type": gpu_type,
**group_record,
"cohort": cohort_title,
"cohort_detail": cohort_detail,
"metric": metric,
"reason": "column missing from loaded records",
"records": len(g),
@@ -65,8 +131,9 @@ def build_plots(df: pd.DataFrame) -> tuple[list, list[dict[str, object]]]:
non_null = int(g[metric].notna().sum())
if non_null == 0:
skipped_metrics.append({
"model_id": model_id,
"gpu_type": gpu_type,
**group_record,
"cohort": cohort_title,
"cohort_detail": cohort_detail,
"metric": metric,
"reason": "no non-null values in loaded records",
"records": len(g),
@@ -79,8 +146,8 @@ def build_plots(df: pd.DataFrame) -> tuple[list, list[dict[str, object]]]:
x="timestamp",
y=metric,
markers=True,
hover_data=["config_id", "commit_sha"],
title=f"{model_id} | {gpu_type} | {metric}",
hover_data=["config_id", "commit_sha", *COMPARISON_COHORT_KEYS],
title=f"{model_id} | {gpu_type} | {cohort_title} | {cohort_detail} | {metric}",
labels={"timestamp": "Time", metric: metric},
)
figs.append(fig)
@@ -95,7 +162,7 @@ def render_skipped_metrics(skipped_metrics: list[dict[str, object]]) -> str:
rows = [
"<h3>Skipped Metric Plots</h3>",
"<table>",
("<thead><tr><th>Model</th><th>GPU</th><th>Metric</th>"
("<thead><tr><th>Model</th><th>GPU</th><th>Cohort</th><th>Metric</th>"
"<th>Records</th><th>Non-null</th><th>Reason</th></tr></thead>"),
"<tbody>",
]
@@ -104,6 +171,7 @@ def render_skipped_metrics(skipped_metrics: list[dict[str, object]]) -> str:
"<tr>"
f"<td>{escape(str(item['model_id']))}</td>"
f"<td>{escape(str(item['gpu_type']))}</td>"
f"<td>{escape(str(item['cohort']))}<br><code>{escape(str(item['cohort_detail']))}</code></td>"
f"<td>{escape(str(item['metric']))}</td>"
f"<td>{item['records']}</td>"
f"<td>{item['non_null']}</td>"
@@ -165,6 +233,7 @@ def main() -> None:
print("Skipped metric plots:")
for item in skipped_metrics:
print(f" - {item['model_id']} | {item['gpu_type']} | "
f"{item['cohort']} | {item['cohort_detail']} | "
f"{item['metric']}: {item['reason']} "
f"({item['non_null']}/{item['records']} non-null)")
print(f"Generated {len(figs)} metric plot(s)")
+435
View File
@@ -0,0 +1,435 @@
# SPDX-License-Identifier: Apache-2.0
"""Comparable identity helpers for performance benchmark records."""
from __future__ import annotations
import dataclasses
import enum
import hashlib
import importlib.metadata
import json
import os
import platform
import re
from collections.abc import Mapping, Sequence
from pathlib import Path
from typing import Any
import torch
RECIPE_SCHEMA_VERSION = 1
PROFILE_ID_LENGTH = 16
_PATH_EXCLUDED_GENERATION_KEYS = {"output_path", "output_video_name"}
_PROMPT_KEYS = {"prompt", "negative_prompt", "neg_prompt"}
REQUIRED_BENCHMARK_IDENTITY_KEYS = (
"benchmark_id",
"workload_id",
"variant_id",
"benchmark_version",
)
_PROFILE_ENV_VARS = (
"CUDA_VISIBLE_DEVICES",
"FASTVIDEO_ATTENTION_BACKEND",
"IMAGE_VERSION",
"UV_TORCH_BACKEND",
)
_PACKAGE_DISTRIBUTIONS = {
"fastvideo_kernel": ("fastvideo-kernel", "fastvideo_kernel"),
"flash_attn": ("flash-attn", "flash_attn"),
"flash_attn_4": ("flash-attn-4",),
"flash_attention_fp4": ("flash-attention-fp4",),
"flashinfer": ("flashinfer-python", "flashinfer"),
"nvidia_cutlass_dsl": ("nvidia-cutlass-dsl", "cutlass-dsl"),
"sageattention": ("sageattention",),
"sageattn3": ("sageattn3",),
"triton": ("triton",),
"xformers": ("xformers",),
}
def canonical_json(payload: Any) -> str:
"""Return deterministic, compact JSON for a recipe/profile payload."""
return json.dumps(
_canonicalize(payload),
sort_keys=True,
separators=(",", ":"),
ensure_ascii=True,
)
def sha256_hexdigest(payload: str | bytes) -> str:
if isinstance(payload, str):
payload = payload.encode("utf-8")
return hashlib.sha256(payload).hexdigest()
def recipe_fingerprint(recipe: Mapping[str, Any]) -> str:
# Runtime-resolved values are audit metadata, not identity: hashing the
# resolved HF snapshot revision would churn the cohort (and via the
# no-baseline init path, silently reset the rolling baseline) on ANY
# upstream repo commit, including model-card edits with unchanged
# weights. The fingerprint is pinned to declared inputs only; the
# resolved values stay in the stored recipe for auditability.
pruned = {
key: (dict(value) if isinstance(value, Mapping) else value)
for key, value in recipe.items()
}
model = pruned.get("model")
if isinstance(model, dict):
model.pop("resolved_revision", None)
attention = pruned.get("attention")
if isinstance(attention, dict):
# Same reasoning as resolved_revision: with requested_backend="auto",
# a silent runtime fallback (e.g. a broken flash-attn wheel) must NOT
# open a fresh ungated cohort and seed degraded numbers as the new
# baseline — it should fail the regression gate in the old cohort.
# Intentional backend changes are declared via requested_backend,
# which stays in the hash.
attention.pop("resolved_backend", None)
return sha256_hexdigest(canonical_json(pruned))
def profile_id(prefix: str, profile: Mapping[str, Any]) -> str:
return f"{prefix}-{sha256_hexdigest(canonical_json(profile))[:PROFILE_ID_LENGTH]}"
def hardware_profile_id(profile: Mapping[str, Any]) -> str:
return profile_id("hw", profile)
def software_profile_id(profile: Mapping[str, Any]) -> str:
return profile_id("sw", profile)
def environment_fingerprint(metadata: Mapping[str, Any]) -> str:
return profile_id("env", metadata)
def benchmark_identity_from_config(cfg: Mapping[str, Any]) -> dict[str, Any]:
missing = [
key for key in REQUIRED_BENCHMARK_IDENTITY_KEYS
if _none_if_empty(cfg.get(key)) is None
]
if missing:
raise ValueError("Benchmark config missing required identity fields: " + ", ".join(missing))
return {
key: cfg[key]
for key in REQUIRED_BENCHMARK_IDENTITY_KEYS
}
def build_recipe_from_benchmark_config(
cfg: Mapping[str, Any],
*,
attention_backend: str | None = None,
resolved_attention_backend: Any | None = None,
resolved_model_revision: Any | None = None,
measured_prompts: Sequence[Any] | None = None,
) -> dict[str, Any]:
"""Build a deterministic recipe document from a benchmark config.
The recipe captures fields that describe what benchmark workload was run. It
intentionally excludes timings, timestamps, commit metadata, and output paths.
"""
model = dict(cfg.get("model") or {})
init_kwargs = dict(cfg.get("init_kwargs") or {})
generation_kwargs = dict(cfg.get("generation_kwargs") or {})
benchmark_identity = benchmark_identity_from_config(cfg)
prompts = list(measured_prompts) if measured_prompts is not None else list(
cfg.get("test_prompts") or ["A cinematic video."])
if attention_backend is None:
attention_backend = os.environ.get("FASTVIDEO_ATTENTION_BACKEND")
negative_prompt = generation_kwargs.pop("negative_prompt", generation_kwargs.pop("neg_prompt", None))
generation_recipe = {
key: value
for key, value in generation_kwargs.items()
if key not in _PATH_EXCLUDED_GENERATION_KEYS and key not in _PROMPT_KEYS
}
return {
"recipe_schema_version": RECIPE_SCHEMA_VERSION,
"benchmark": benchmark_identity,
"model": {
"model_path": normalize_model_path(model.get("model_path")),
"revision": _none_if_empty(model.get("revision") or cfg.get("revision")),
"resolved_revision": _none_if_empty(resolved_model_revision),
},
"init_kwargs": init_kwargs,
"generation_kwargs": generation_recipe,
"inputs": {
"prompt_count": len(prompts),
"prompt_sha256": [_optional_digest(prompt) for prompt in prompts],
"negative_prompt_sha256": _optional_digest(negative_prompt),
},
"attention": {
"requested_backend": _none_if_empty(attention_backend) or "auto",
"resolved_backend": _none_if_empty(resolved_attention_backend),
},
}
def normalize_model_path(value: Any) -> str | None:
if value is None:
return None
text = os.fspath(value) if isinstance(value, os.PathLike) else str(value)
text = text.strip()
if not text:
return None
if _looks_like_local_path(text):
return Path(text).expanduser().as_posix()
return re.sub(r"/+", "/", text)
def resolved_revision_from_model_path(value: Any) -> str | None:
normalized = normalize_model_path(value)
if normalized is None:
return None
parts = Path(normalized).parts
for index, part in enumerate(parts[:-1]):
if part == "snapshots":
revision = parts[index + 1]
if re.fullmatch(r"[0-9a-f]{40}", revision):
return revision
return None
def hardware_profile(
*,
num_gpus: int | None = None,
gpu_devices: Sequence[Mapping[str, Any]] | None = None,
device_type: str | None = None,
interconnect: str | None = None,
) -> dict[str, Any]:
"""Return normalized hardware cohort metadata.
``gpu_devices`` is an injection point for tests and non-CUDA callers.
"""
if gpu_devices is not None:
devices = [_normalize_gpu_device(device) for device in gpu_devices]
return {
"device_type": device_type or "cuda",
"gpu_count": num_gpus if num_gpus is not None else len(devices),
"gpus": devices,
"interconnect": interconnect or "unknown",
}
if not torch.cuda.is_available():
return {
"device_type": device_type or "cpu",
"gpu_count": 0,
"gpus": [],
"interconnect": interconnect or "none",
}
gpu_count = int(num_gpus if num_gpus is not None else torch.cuda.device_count())
devices = []
cuda_device_count = torch.cuda.device_count()
for device_id in range(gpu_count):
if device_id >= cuda_device_count:
devices.append(_normalize_gpu_device({}))
continue
props = torch.cuda.get_device_properties(device_id)
capability = torch.cuda.get_device_capability(device_id)
devices.append(
_normalize_gpu_device({
"name": props.name,
"memory_bytes": props.total_memory,
"compute_capability": f"{capability[0]}.{capability[1]}",
}))
return {
"device_type": device_type or "cuda",
"gpu_count": gpu_count,
"gpus": devices,
"interconnect": interconnect or _detect_cuda_interconnect(gpu_count),
}
def software_profile(
*,
python_version: str | None = None,
torch_version: str | None = None,
cuda_version: str | None = None,
package_versions: Mapping[str, str | None] | None = None,
) -> dict[str, Any]:
"""Return normalized software cohort metadata.
Python, PyTorch, and CUDA use major/minor cohorts. Attention and kernel
packages keep exact versions because patch releases can change performance.
"""
versions = dict(package_versions) if package_versions is not None else _installed_package_versions()
return {
"python": _major_minor(python_version or platform.python_version()),
"pytorch": _major_minor(torch_version or torch.__version__),
"cuda": _major_minor(cuda_version or torch.version.cuda),
"packages": {
name: str(version)
for name, version in sorted(versions.items())
if version
},
}
def environment_metadata(
*,
env: Mapping[str, str] | None = None,
package_versions: Mapping[str, str | None] | None = None,
hardware: Mapping[str, Any] | None = None,
software: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
"""Return audit metadata kept separate from comparable identity keys."""
source_env = env if env is not None else os.environ
full_package_versions = dict(package_versions) if package_versions is not None else _installed_package_versions()
return {
"python": {
"version": platform.python_version(),
"implementation": platform.python_implementation(),
},
"platform": {
"system": platform.system(),
"machine": platform.machine(),
"release": platform.release(),
},
"torch": {
"version": torch.__version__,
"cuda": torch.version.cuda,
"cudnn": torch.backends.cudnn.version(),
},
"env": {
key: source_env.get(key)
for key in _PROFILE_ENV_VARS
if source_env.get(key) is not None
},
"packages": {
key: value
for key, value in sorted(full_package_versions.items())
if value
},
"hardware_profile": dict(hardware) if hardware is not None else hardware_profile(),
"software_profile": dict(software) if software is not None else software_profile(
package_versions=full_package_versions),
}
def _canonicalize(value: Any) -> Any:
if dataclasses.is_dataclass(value) and not isinstance(value, type):
return _canonicalize(dataclasses.asdict(value))
if isinstance(value, Mapping):
return {
str(key): _canonicalize(value[key])
for key in sorted(value, key=lambda item: str(item))
}
if isinstance(value, (set, frozenset)):
return [_canonicalize(item) for item in sorted(value, key=lambda item: str(item))]
if isinstance(value, tuple):
return [_canonicalize(item) for item in value]
if isinstance(value, list):
return [_canonicalize(item) for item in value]
if isinstance(value, enum.Enum):
return value.name
if isinstance(value, Path):
return value.expanduser().as_posix()
if isinstance(value, os.PathLike):
return os.fspath(value)
if isinstance(value, type):
return f"{value.__module__}.{value.__qualname__}"
if isinstance(value, torch.dtype):
return str(value).removeprefix("torch.")
return value
def _none_if_empty(value: Any) -> Any | None:
if value is None:
return None
if isinstance(value, str) and not value.strip():
return None
return value
def _optional_digest(value: Any) -> str | None:
if value is None:
return None
return sha256_hexdigest(str(value))
def _looks_like_local_path(value: str) -> bool:
if value.startswith(("/", "./", "../", "~")):
return True
looks_like_hf_repo = re.match(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$", value)
has_separator = "/" in value or "\\" in value or os.sep in value
return has_separator and looks_like_hf_repo is None
def _normalize_gpu_device(device: Mapping[str, Any]) -> dict[str, Any]:
memory_gb = device.get("memory_gb")
if memory_gb is None and device.get("memory_bytes") is not None:
memory_gb = round(float(device["memory_bytes"]) / (1024**3))
return {
"name": _none_if_empty(device.get("name")) or "unknown",
"memory_gb": memory_gb,
"compute_capability": _none_if_empty(device.get("compute_capability")),
}
def _detect_cuda_interconnect(gpu_count: int) -> str:
if gpu_count <= 1:
return "single_gpu"
try:
from fastvideo.platforms import current_platform
if current_platform.is_cuda() and current_platform.is_full_nvlink(list(range(gpu_count))):
return "full_nvlink"
return "none_or_partial"
except Exception:
return "unknown"
def _installed_package_versions() -> dict[str, str | None]:
return {
name: _distribution_version(*distribution_names)
for name, distribution_names in _PACKAGE_DISTRIBUTIONS.items()
}
def _distribution_version(*names: str) -> str | None:
for name in names:
try:
return importlib.metadata.version(name)
except importlib.metadata.PackageNotFoundError:
continue
return None
def _major_minor(version: str | None) -> str | None:
if not version:
return None
match = re.search(r"(\d+)\.(\d+)", version)
if match is None:
return version
return f"{match.group(1)}.{match.group(2)}"
__all__ = [
"RECIPE_SCHEMA_VERSION",
"REQUIRED_BENCHMARK_IDENTITY_KEYS",
"benchmark_identity_from_config",
"canonical_json",
"environment_fingerprint",
"environment_metadata",
"hardware_profile",
"hardware_profile_id",
"normalize_model_path",
"profile_id",
"recipe_fingerprint",
"resolved_revision_from_model_path",
"build_recipe_from_benchmark_config",
"sha256_hexdigest",
"software_profile",
"software_profile_id",
]
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
import pytest
from fastvideo.tests.performance import compare_baseline
from fastvideo.performance.metric_policy import resolve_metric_policies
@@ -119,6 +121,110 @@ def test_boolean_regression_threshold_values_are_ignored():
assert latency.threshold_percent == 0.08
assert latency.threshold_absolute == 0.5
assert latency.gated is False
def test_normalized_record_preserves_identity_metadata(monkeypatch):
monkeypatch.setenv("PERF_RUN_SOURCE", "pr")
raw = _raw_result()
raw.update({
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
"recipe": {
"recipe_schema_version": 1,
},
"recipe_fingerprint": "recipe-1",
"hardware_profile": {
"gpu_count": 1,
},
"hardware_profile_id": "hw-1",
"software_profile": {
"python": "3.12",
},
"software_profile_id": "sw-1",
"environment_metadata": {
"env": {
"IMAGE_VERSION": "latest",
},
},
"environment_fingerprint": "env-1",
})
record = compare_baseline.normalize_performance_result(raw)
assert record["workload_id"] == "wan-t2v"
assert record["variant_id"] == "1.3b-sp2"
assert record["benchmark_version"] == 2
assert record["recipe"] == {"recipe_schema_version": 1}
assert record["recipe_fingerprint"] == "recipe-1"
assert record["hardware_profile"] == {"gpu_count": 1}
assert record["hardware_profile_id"] == "hw-1"
assert record["software_profile"] == {"python": "3.12"}
assert record["software_profile_id"] == "sw-1"
assert record["environment_metadata"] == {"env": {"IMAGE_VERSION": "latest"}}
assert record["environment_fingerprint"] == "env-1"
def test_normalized_record_reads_identity_labels_from_recipe(monkeypatch):
monkeypatch.setenv("PERF_RUN_SOURCE", "pr")
raw = _raw_result()
raw["recipe"] = {
"benchmark": {
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
},
}
record = compare_baseline.normalize_performance_result(raw)
assert record["workload_id"] == "wan-t2v"
assert record["variant_id"] == "1.3b-sp2"
assert record["benchmark_version"] == 2
def test_comparison_identity_filters_use_full_issue_key():
record = {
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
"recipe_fingerprint": "recipe-1",
"hardware_profile_id": "hw-1",
"software_profile_id": "sw-1",
}
assert compare_baseline._comparison_identity_filters(record) == {
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": "2",
"recipe_fingerprint": "recipe-1",
"hardware_profile_id": "hw-1",
"software_profile_id": "sw-1",
}
def test_comparison_identity_filters_require_full_issue_key():
record = {
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 0,
"recipe_fingerprint": "recipe-1",
"hardware_profile_id": "hw-1",
}
with pytest.raises(ValueError, match="software_profile_id"):
compare_baseline._comparison_identity_filters(record)
def test_comparison_identity_filters_keep_zero_version():
record = {
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 0,
"recipe_fingerprint": "recipe-1",
"hardware_profile_id": "hw-1",
"software_profile_id": "sw-1",
}
assert compare_baseline._comparison_identity_filters(record)["benchmark_version"] == "0"
def test_baseline_eligibility_only_for_successful_scheduled_main():
@@ -56,8 +56,34 @@ def _record(model_id, gpu_type, ts, commit, latency, throughput, success=True, *
def test_summary_endpoint_returns_latest_group_status():
app = create_app(FakeStore([
_record("wan", "NVIDIA L40S", "2026-01-01T00:00:00+00:00", "a" * 40, 10.0, 10.0),
_record("wan", "NVIDIA L40S", "2026-01-02T00:00:00+00:00", "b" * 40, 11.0, 9.0),
_record(
"wan",
"NVIDIA L40S",
"2026-01-01T00:00:00+00:00",
"a" * 40,
10.0,
10.0,
workload_id="wan-t2v",
variant_id="1.3b-sp2",
benchmark_version=2,
recipe_fingerprint="recipe-a",
hardware_profile_id="hw-l40s",
software_profile_id="sw-cu130",
),
_record(
"wan",
"NVIDIA L40S",
"2026-01-02T00:00:00+00:00",
"b" * 40,
11.0,
9.0,
workload_id="wan-t2v",
variant_id="1.3b-sp2",
benchmark_version=2,
recipe_fingerprint="recipe-a",
hardware_profile_id="hw-l40s",
software_profile_id="sw-cu130",
),
]))
client = TestClient(app)
@@ -71,6 +97,10 @@ def test_summary_endpoint_returns_latest_group_status():
assert body["rows"][0]["metrics"]["latency"]["threshold_exceeded"] is True
assert body["rows"][0]["threshold_exceeded_metrics"] == ["latency", "throughput"]
assert body["rows"][0]["computed_regression_status"] == "fail"
assert body["rows"][0]["workload_id"] == "wan-t2v"
assert body["rows"][0]["variant_id"] == "1.3b-sp2"
assert body["rows"][0]["benchmark_version"] == 2
assert body["rows"][0]["recipe_fingerprint"] == "recipe-a"
def test_summary_status_is_independent_of_days_window():
@@ -0,0 +1,98 @@
# SPDX-License-Identifier: Apache-2.0
import pandas as pd
from fastvideo.tests.performance import dashboard
def _record(recipe_fingerprint="recipe-a", software_profile_id="sw-a", **overrides):
record = {
"model_id": "wan-t2v-1.3b-2gpu",
"gpu_type": "NVIDIA L40S",
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
"recipe_fingerprint": recipe_fingerprint,
"hardware_profile_id": "hw-l40s",
"software_profile_id": software_profile_id,
"timestamp": "2026-01-01T00:00:00+00:00",
"commit_sha": "a" * 40,
"config_id": "aaaaaaa",
"latency": 10.0,
"throughput": 4.5,
"memory": 10000.0,
"text_encoder_time_s": 2.0,
"dit_time_s": 8.0,
"vae_decode_time_s": 3.0,
}
record.update(overrides)
return record
def test_group_data_uses_full_comparison_cohort():
df = pd.DataFrame([
_record(recipe_fingerprint="recipe-a", software_profile_id="sw-a"),
_record(recipe_fingerprint="recipe-b", software_profile_id="sw-b"),
])
groups = list(dashboard.group_data(df))
assert len(groups) == 2
assert {key[5] for key, _group in groups} == {"recipe-a", "recipe-b"}
assert {key[7] for key, _group in groups} == {"sw-a", "sw-b"}
def test_group_data_fills_legacy_cohort_columns():
df = pd.DataFrame([{
"model_id": "legacy-wan",
"gpu_type": "NVIDIA L40S",
"timestamp": "2026-01-01T00:00:00+00:00",
"commit_sha": "a" * 40,
"config_id": "aaaaaaa",
"latency": 10.0,
}])
groups = list(dashboard.group_data(df))
assert len(groups) == 1
assert groups[0][0][2:] == ("", "", "", "", "", "")
def test_build_plots_labels_distinct_cohorts():
df = pd.DataFrame([
_record(recipe_fingerprint="recipe-a", software_profile_id="sw-a"),
_record(recipe_fingerprint="recipe-b", software_profile_id="sw-b"),
])
figs, skipped_metrics = dashboard.build_plots(df)
titles = [fig.layout.title.text for fig in figs]
assert len(figs) == len(dashboard.METRICS) * 2
assert skipped_metrics == []
assert any("recipe recipe-a | hw-l40s | sw-a" in title for title in titles)
assert any("recipe recipe-b | hw-l40s | sw-b" in title for title in titles)
def test_skipped_metric_table_includes_cohort_identity():
df = pd.DataFrame([{
"model_id": "wan-t2v-1.3b-2gpu",
"gpu_type": "NVIDIA L40S",
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
"recipe_fingerprint": "recipe-a",
"hardware_profile_id": "hw-l40s",
"software_profile_id": "sw-a",
"timestamp": "2026-01-01T00:00:00+00:00",
"commit_sha": "a" * 40,
"config_id": "aaaaaaa",
"latency": 10.0,
}])
_figs, skipped_metrics = dashboard.build_plots(df)
html = dashboard.render_skipped_metrics(skipped_metrics[:1])
assert skipped_metrics[0]["cohort"] == "wan-t2v / 1.3b-sp2 / v2"
assert skipped_metrics[0]["cohort_detail"] == "recipe recipe-a | hw-l40s | sw-a"
assert "wan-t2v / 1.3b-sp2 / v2" in html
assert "recipe recipe-a | hw-l40s | sw-a" in html
@@ -142,6 +142,111 @@ def test_build_latest_summary_separates_informational_threshold_crossing():
assert rows[0]["threshold_exceeded_metrics"] == ["latency"]
assert rows[0]["failing_metrics"] == []
assert rows[0]["computed_regression_status"] == "pass"
def test_build_latest_summary_keeps_identity_cohorts_separate():
records = [
_record(
"2026-01-01T00:00:00+00:00",
"a" * 40,
10.0,
10.0,
recipe_fingerprint="recipe-a",
workload_id="wan-t2v",
variant_id="1.3b-sp2",
benchmark_version=2,
hardware_profile_id="hw-l40s",
software_profile_id="sw-cu130",
run_source="scheduled_main",
baseline_eligible=True,
),
_record(
"2026-01-02T00:00:00+00:00",
"b" * 40,
20.0,
5.0,
recipe_fingerprint="recipe-b",
workload_id="wan-t2v",
variant_id="1.3b-sp2",
benchmark_version=2,
hardware_profile_id="hw-l40s",
software_profile_id="sw-cu130",
run_source="scheduled_main",
baseline_eligible=True,
),
_record(
"2026-01-03T00:00:00+00:00",
"c" * 40,
22.0,
4.5,
recipe_fingerprint="recipe-b",
workload_id="wan-t2v",
variant_id="1.3b-sp2",
benchmark_version=2,
hardware_profile_id="hw-l40s",
software_profile_id="sw-cu130",
),
]
rows = build_latest_summary(records, max_regression=0.05)
recipe_b_row = next(row for row in rows if row["recipe_fingerprint"] == "recipe-b")
assert len(rows) == 2
assert recipe_b_row["baseline_n"] == 1
assert recipe_b_row["metrics"]["latency"]["baseline"] == 20.0
assert recipe_b_row["workload_id"] == "wan-t2v"
assert recipe_b_row["variant_id"] == "1.3b-sp2"
assert recipe_b_row["benchmark_version"] == 2
def test_build_latest_summary_keeps_variant_versions_separate():
records = [
_record(
"2026-01-01T00:00:00+00:00",
"a" * 40,
10.0,
10.0,
workload_id="wan-t2v",
variant_id="1.3b-sp2",
benchmark_version=1,
recipe_fingerprint="recipe-a",
hardware_profile_id="hw-l40s",
software_profile_id="sw-cu130",
run_source="scheduled_main",
baseline_eligible=True,
),
_record(
"2026-01-02T00:00:00+00:00",
"b" * 40,
20.0,
5.0,
workload_id="wan-t2v",
variant_id="1.3b-sp2",
benchmark_version=2,
recipe_fingerprint="recipe-a",
hardware_profile_id="hw-l40s",
software_profile_id="sw-cu130",
run_source="scheduled_main",
baseline_eligible=True,
),
_record(
"2026-01-03T00:00:00+00:00",
"c" * 40,
22.0,
4.5,
workload_id="wan-t2v",
variant_id="1.3b-sp2",
benchmark_version=2,
recipe_fingerprint="recipe-a",
hardware_profile_id="hw-l40s",
software_profile_id="sw-cu130",
),
]
rows = build_latest_summary(records, max_regression=0.05)
version_2_row = next(row for row in rows if row["benchmark_version"] == 2)
assert len(rows) == 2
assert version_2_row["baseline_n"] == 1
assert version_2_row["metrics"]["latency"]["baseline"] == 20.0
def test_filter_records_and_trends_preserve_metric_points():
@@ -218,3 +323,78 @@ def test_load_records_can_filter_baseline_eligible_records(tmp_path):
"2026-01-02T00:00:00+00:00",
"2026-01-03T00:00:00+00:00",
}
def test_load_records_for_model_filters_identity_cohort(tmp_path):
model_dir = tmp_path / "wan"
model_dir.mkdir()
(model_dir / "matching.json").write_text(
"""
{
"model_id": "wan",
"gpu_type": "NVIDIA L40S",
"timestamp": "2026-01-01T00:00:00+00:00",
"success": true,
"baseline_eligible": true,
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
"recipe_fingerprint": "recipe-a",
"hardware_profile_id": "hw-l40s",
"software_profile_id": "sw-cu130"
}
""",
encoding="utf-8",
)
(model_dir / "other_recipe.json").write_text(
"""
{
"model_id": "wan",
"gpu_type": "NVIDIA L40S",
"timestamp": "2026-01-02T00:00:00+00:00",
"success": true,
"baseline_eligible": true,
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
"recipe_fingerprint": "recipe-b",
"hardware_profile_id": "hw-l40s",
"software_profile_id": "sw-cu130"
}
""",
encoding="utf-8",
)
(model_dir / "other_version.json").write_text(
"""
{
"model_id": "wan",
"gpu_type": "NVIDIA L40S",
"timestamp": "2026-01-03T00:00:00+00:00",
"success": true,
"baseline_eligible": true,
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 3,
"recipe_fingerprint": "recipe-a",
"hardware_profile_id": "hw-l40s",
"software_profile_id": "sw-cu130"
}
""",
encoding="utf-8",
)
records = hf_store.load_records_for_model(
str(tmp_path),
"wan",
"NVIDIA L40S",
workload_id="wan-t2v",
variant_id="1.3b-sp2",
benchmark_version="2",
recipe_fingerprint="recipe-a",
hardware_profile_id="hw-l40s",
software_profile_id="sw-cu130",
baseline_eligible_only=True,
)
assert len(records) == 1
assert records[0]["recipe_fingerprint"] == "recipe-a"
@@ -0,0 +1,422 @@
# SPDX-License-Identifier: Apache-2.0
from copy import deepcopy
import pytest
import torch
from fastvideo.tests.performance import identity as identity_module
from fastvideo.tests.performance.identity import (
build_recipe_from_benchmark_config,
canonical_json,
environment_fingerprint,
environment_metadata,
hardware_profile,
hardware_profile_id,
recipe_fingerprint,
resolved_revision_from_model_path,
software_profile,
software_profile_id,
)
def _benchmark_config():
return {
"benchmark_id": "wan-t2v-1.3b-2gpu",
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
"model": {
"model_path": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
"model_short_name": "Wan2.1-T2V-1.3B",
},
"init_kwargs": {
"num_gpus": 2,
"sp_size": 2,
"tp_size": 1,
"vae_sp": True,
"vae_tiling": True,
"text_encoder_precisions": ["fp32"],
},
"generation_kwargs": {
"height": 480,
"width": 832,
"num_frames": 45,
"num_inference_steps": 4,
"guidance_scale": 3,
"embedded_cfg_scale": 6,
"seed": 1024,
"fps": 24,
"neg_prompt": "low quality",
},
"test_prompts": ["A cinematic video."],
}
def _fingerprint(cfg, attention_backend="FLASH_ATTN"):
recipe = build_recipe_from_benchmark_config(cfg, attention_backend=attention_backend)
return recipe_fingerprint(recipe)
def test_canonical_json_is_stable_for_mapping_order():
left = {"b": [2, 1], "a": {"z": "last", "m": "middle"}}
right = {"a": {"m": "middle", "z": "last"}, "b": [2, 1]}
assert canonical_json(left) == canonical_json(right)
def test_canonical_json_handles_sets_deterministically():
assert canonical_json({"values": {"b", "a"}}) == '{"values":["a","b"]}'
assert canonical_json({"values": frozenset(("b", "a"))}) == '{"values":["a","b"]}'
def test_same_config_produces_same_recipe_fingerprint():
cfg = _benchmark_config()
assert _fingerprint(cfg) == _fingerprint(deepcopy(cfg))
def test_recipe_includes_first_class_benchmark_identity():
recipe = build_recipe_from_benchmark_config(_benchmark_config())
assert recipe["benchmark"] == {
"benchmark_id": "wan-t2v-1.3b-2gpu",
"workload_id": "wan-t2v",
"variant_id": "1.3b-sp2",
"benchmark_version": 2,
}
def test_recipe_requires_first_class_benchmark_identity():
cfg = _benchmark_config()
del cfg["workload_id"]
with pytest.raises(ValueError, match="workload_id"):
build_recipe_from_benchmark_config(cfg)
def test_semantically_equivalent_sequence_forms_match():
cfg = _benchmark_config()
equivalent = deepcopy(cfg)
equivalent["init_kwargs"]["text_encoder_precisions"] = tuple(
equivalent["init_kwargs"]["text_encoder_precisions"])
assert _fingerprint(cfg) == _fingerprint(equivalent)
def test_output_path_does_not_change_recipe_fingerprint():
cfg = _benchmark_config()
with_output_path = deepcopy(cfg)
with_output_path["generation_kwargs"]["output_path"] = "/tmp/generated"
assert _fingerprint(cfg) == _fingerprint(with_output_path)
def test_runtime_resolved_values_do_not_change_recipe_fingerprint():
# Runtime-resolved values are audit metadata: an upstream repo commit
# (even a README-only edit) or a silent attention-backend fallback must
# not churn the cohort — that would open a fresh ungated cohort and let
# degraded numbers seed a new baseline. Declared inputs (including
# requested_backend) stay in the hash.
cfg = _benchmark_config()
recipe = build_recipe_from_benchmark_config(
cfg, attention_backend="FLASH_ATTN",
resolved_attention_backend="FLASH_ATTN",
resolved_model_revision="aaaa1111")
changed = build_recipe_from_benchmark_config(
cfg, attention_backend="FLASH_ATTN",
resolved_attention_backend="TORCH_SDPA",
resolved_model_revision="bbbb2222")
assert recipe["model"]["resolved_revision"] == "aaaa1111" # kept for audit
assert recipe["attention"]["resolved_backend"] == "FLASH_ATTN" # kept for audit
assert recipe_fingerprint(recipe) == recipe_fingerprint(changed)
def test_measured_prompt_override_ignores_unused_configured_prompts():
cfg = _benchmark_config()
changed = deepcopy(cfg)
changed["test_prompts"] = [
"Unused prompt that should not affect this measured workload.",
cfg["test_prompts"][0],
]
recipe = build_recipe_from_benchmark_config(cfg, measured_prompts=[cfg["test_prompts"][0]])
changed_recipe = build_recipe_from_benchmark_config(changed, measured_prompts=[cfg["test_prompts"][0]])
assert recipe_fingerprint(recipe) == recipe_fingerprint(changed_recipe)
def test_num_inference_steps_changes_recipe_fingerprint():
cfg = _benchmark_config()
changed = deepcopy(cfg)
changed["generation_kwargs"]["num_inference_steps"] = 8
assert _fingerprint(cfg) != _fingerprint(changed)
def test_benchmark_version_changes_recipe_fingerprint():
cfg = _benchmark_config()
changed = deepcopy(cfg)
changed["benchmark_version"] = 3
assert _fingerprint(cfg) != _fingerprint(changed)
def test_attention_backend_changes_recipe_fingerprint():
cfg = _benchmark_config()
assert _fingerprint(cfg, attention_backend="FLASH_ATTN") != _fingerprint(
cfg, attention_backend="TORCH_SDPA")
def test_precision_changes_recipe_fingerprint():
cfg = _benchmark_config()
changed = deepcopy(cfg)
changed["init_kwargs"]["text_encoder_precisions"] = ["bf16"]
assert _fingerprint(cfg) != _fingerprint(changed)
def test_dimensions_and_seed_change_recipe_fingerprint():
cfg = _benchmark_config()
different_dimensions = deepcopy(cfg)
different_dimensions["generation_kwargs"]["width"] = 1024
different_seed = deepcopy(cfg)
different_seed["generation_kwargs"]["seed"] = 2048
assert _fingerprint(cfg) != _fingerprint(different_dimensions)
assert _fingerprint(cfg) != _fingerprint(different_seed)
def test_distributed_layout_changes_recipe_fingerprint():
cfg = _benchmark_config()
changed = deepcopy(cfg)
changed["init_kwargs"]["sp_size"] = 1
changed["init_kwargs"]["tp_size"] = 2
assert _fingerprint(cfg) != _fingerprint(changed)
def test_software_profile_uses_exact_attention_kernel_package_versions_for_id():
profile_a = software_profile(
python_version="3.12.4",
torch_version="2.12.0+cu130",
cuda_version="13.0",
package_versions={
"triton": "3.4.1",
"fastvideo_kernel": "0.3.2",
"flash_attn_4": "4.0.0.dev0",
"flashinfer": "0.2.11",
"nvidia_cutlass_dsl": "4.5.0",
},
)
profile_b = software_profile(
python_version="3.12.5",
torch_version="2.12.1+cu130",
cuda_version="13.0",
package_versions={
"triton": "3.4.9",
"fastvideo_kernel": "0.3.7",
"flash_attn_4": "4.0.0.dev1",
"flashinfer": "0.2.12",
"nvidia_cutlass_dsl": "4.5.1",
},
)
assert profile_a == {
"python": "3.12",
"pytorch": "2.12",
"cuda": "13.0",
"packages": {
"fastvideo_kernel": "0.3.2",
"flash_attn_4": "4.0.0.dev0",
"flashinfer": "0.2.11",
"nvidia_cutlass_dsl": "4.5.0",
"triton": "3.4.1",
},
}
assert profile_b["python"] == "3.12"
assert profile_b["pytorch"] == "2.12"
assert profile_b["cuda"] == "13.0"
assert software_profile_id(profile_a) != software_profile_id(profile_b)
def test_installed_package_versions_checks_attention_kernel_distributions(monkeypatch):
versions = {
"fastvideo-kernel": "0.3.2",
"flash-attn": "2.8.1",
"flash-attn-4": "4.0.0.dev0",
"flash-attention-fp4": "0.1.0",
"flashinfer-python": "0.2.11",
"nvidia-cutlass-dsl": "4.5.0",
}
def fake_version(name):
if name not in versions:
raise identity_module.importlib.metadata.PackageNotFoundError(name)
return versions[name]
monkeypatch.setattr(identity_module.importlib.metadata, "version", fake_version)
installed = identity_module._installed_package_versions()
assert installed["fastvideo_kernel"] == "0.3.2"
assert installed["flash_attn"] == "2.8.1"
assert installed["flash_attn_4"] == "4.0.0.dev0"
assert installed["flash_attention_fp4"] == "0.1.0"
assert installed["flashinfer"] == "0.2.11"
assert installed["nvidia_cutlass_dsl"] == "4.5.0"
def test_hardware_profile_id_uses_normalized_gpu_cohort():
profile = hardware_profile(
num_gpus=2,
gpu_devices=[
{
"name": "NVIDIA L40S",
"memory_bytes": 48 * 1024**3,
"compute_capability": "8.9",
},
{
"name": "NVIDIA L40S",
"memory_gb": 48,
"compute_capability": "8.9",
},
],
interconnect="none_or_partial",
)
assert profile == {
"device_type": "cuda",
"gpu_count": 2,
"gpus": [
{
"name": "NVIDIA L40S",
"memory_gb": 48,
"compute_capability": "8.9",
},
{
"name": "NVIDIA L40S",
"memory_gb": 48,
"compute_capability": "8.9",
},
],
"interconnect": "none_or_partial",
}
assert hardware_profile_id(profile).startswith("hw-")
def test_hardware_profile_pads_missing_requested_cuda_devices(monkeypatch):
class Props:
name = "NVIDIA L40S"
total_memory = 48 * 1024**3
monkeypatch.setattr(torch.cuda, "is_available", lambda: True)
monkeypatch.setattr(torch.cuda, "device_count", lambda: 1)
monkeypatch.setattr(torch.cuda, "get_device_properties", lambda device_id: Props())
monkeypatch.setattr(torch.cuda, "get_device_capability", lambda device_id: (8, 9))
profile = hardware_profile(num_gpus=2, interconnect="unknown")
assert profile["gpu_count"] == 2
assert profile["gpus"] == [
{
"name": "NVIDIA L40S",
"memory_gb": 48,
"compute_capability": "8.9",
},
{
"name": "unknown",
"memory_gb": None,
"compute_capability": None,
},
]
def test_local_path_detection_checks_both_path_separators(monkeypatch):
monkeypatch.setattr(identity_module.os, "sep", "\\")
assert identity_module._looks_like_local_path("models/local/checkpoint") is True
assert identity_module._looks_like_local_path(r"C:\models\checkpoint") is True
assert identity_module._looks_like_local_path("Wan-AI/Wan2.1-T2V-1.3B-Diffusers") is False
def test_environment_metadata_is_separate_audit_fingerprint():
cfg = _benchmark_config()
recipe_hash = _fingerprint(cfg)
audit_a = environment_metadata(
env={"IMAGE_VERSION": "py3.12-cuda13.0.0"},
package_versions={"triton": "3.4.1"},
hardware={"device_type": "cuda", "gpu_count": 2},
software={"python": "3.12", "pytorch": "2.12", "cuda": "13.0"},
)
audit_b = environment_metadata(
env={"IMAGE_VERSION": "py3.12-cuda13.0.1"},
package_versions={"triton": "3.4.1"},
hardware={"device_type": "cuda", "gpu_count": 2},
software={"python": "3.12", "pytorch": "2.12", "cuda": "13.0"},
)
assert recipe_hash == _fingerprint(cfg)
assert environment_fingerprint(audit_a) != environment_fingerprint(audit_b)
def test_resolved_revision_from_hf_snapshot_path():
revision = "a" * 40
assert resolved_revision_from_model_path(
f"/root/.cache/huggingface/hub/models--org--repo/snapshots/{revision}") == revision
assert resolved_revision_from_model_path("/models/local-repo") is None
def test_runtime_identity_from_generator_summarizes_worker_records():
from fastvideo.tests.performance.test_inference_performance import _runtime_identity_from_generator
revision = "b" * 40
class FakeExecutor:
def collective_rpc(self, _method):
return [
{
"resolved_attention_backends": ["FLASH_ATTN"],
"resolved_model_path": f"/root/.cache/huggingface/hub/models--org--repo/snapshots/{revision}",
},
{
"resolved_attention_backends": ["FLASH_ATTN"],
"resolved_model_path": f"/root/.cache/huggingface/hub/models--org--repo/snapshots/{revision}",
},
]
class FakeGenerator:
executor = FakeExecutor()
assert _runtime_identity_from_generator(FakeGenerator()) == {
"resolved_attention_backend": "FLASH_ATTN",
"resolved_model_revision": revision,
}
def test_collect_worker_identity_handles_torch_module_pipeline():
from fastvideo.tests.performance.test_inference_performance import _collect_worker_identity
class Backend:
name = "FLASH_ATTN"
class Leaf(torch.nn.Module):
backend = Backend()
class Pipeline(torch.nn.Module):
model_path = "/models/local"
def __init__(self):
super().__init__()
self.leaf = Leaf()
class Worker:
pipeline = Pipeline()
assert _collect_worker_identity(Worker()) == {
"resolved_attention_backends": ["FLASH_ATTN"],
"resolved_model_path": "/models/local",
}
@@ -12,12 +12,25 @@ import os
import time
from collections.abc import Mapping
from datetime import datetime, timezone
from typing import Any
import torch
import pytest
from fastvideo import VideoGenerator
from fastvideo.logger import init_logger
from fastvideo.tests.performance.identity import (
benchmark_identity_from_config,
build_recipe_from_benchmark_config,
environment_fingerprint,
environment_metadata,
hardware_profile,
hardware_profile_id,
recipe_fingerprint,
resolved_revision_from_model_path,
software_profile,
software_profile_id,
)
from fastvideo.worker.multiproc_executor import MultiprocExecutor
logger = init_logger(__name__)
@@ -34,8 +47,11 @@ V2_REQUIRED_IDENTITY_FIELDS = (
"variant_id",
"benchmark_version",
)
# "recipe" is no longer config-declarable: the fingerprint follow-up landed,
# and the generated recipe document owns that key in emitted records. A config
# declaring it now fails validation loudly instead of being silently
# overwritten by the generated one.
V2_OPTIONAL_METADATA_FIELDS = (
"recipe",
"metric_threshold_policy",
"quality_metadata",
)
@@ -227,6 +243,110 @@ def _write_results(results):
logger.info("Performance results written to %s", filepath)
def _backend_name(value) -> str:
if hasattr(value, "name"):
return str(value.name)
return str(value)
def _collect_worker_identity(worker) -> dict[str, Any]:
pipeline = getattr(worker, "pipeline", None)
model_path = getattr(pipeline, "model_path", None)
backends: set[str] = set()
modules = getattr(pipeline, "modules", None)
if isinstance(modules, Mapping):
module_iter = modules.values()
elif isinstance(pipeline, torch.nn.Module):
module_iter = (pipeline,)
else:
module_iter = ()
for module in module_iter:
if isinstance(module, torch.nn.Module):
for submodule in module.modules():
backend = getattr(submodule, "backend", None)
if backend is not None:
backends.add(_backend_name(backend))
else:
backend = getattr(module, "backend", None)
if backend is not None:
backends.add(_backend_name(backend))
return {
"resolved_attention_backends": sorted(backends),
"resolved_model_path": model_path,
}
def _single_or_list(values: set[str]) -> str | list[str] | None:
ordered = sorted(values)
if not ordered:
return None
if len(ordered) == 1:
return ordered[0]
return ordered
def _runtime_identity_from_generator(generator) -> dict[str, Any]:
worker_records = generator.executor.collective_rpc(_collect_worker_identity)
resolved_backends: set[str] = set()
resolved_revisions: set[str] = set()
for record in worker_records:
resolved_backends.update(record.get("resolved_attention_backends") or [])
revision = resolved_revision_from_model_path(record.get("resolved_model_path"))
if revision is not None:
resolved_revisions.add(revision)
return {
"resolved_attention_backend": _single_or_list(resolved_backends),
"resolved_model_revision": _single_or_list(resolved_revisions),
}
def _benchmark_identity_fields(cfg):
identity = benchmark_identity_from_config(cfg)
return {
key: identity[key]
for key in ("workload_id", "variant_id", "benchmark_version")
}
def _build_identity_fields(cfg, init_kwargs, prompt, runtime_identity):
# Legacy v1 configs (no config_schema_version) stay on the legacy
# (model_id, gpu_type) cohort: they lack the required identity fields,
# and building a recipe for them would raise AFTER the GPU measurement
# completed. The validator already enforces that v2 configs carry the
# identity fields, so v2 records always get the full identity block.
if cfg.get("config_schema_version") is None:
return {}
recipe = build_recipe_from_benchmark_config(
cfg,
resolved_attention_backend=runtime_identity.get("resolved_attention_backend"),
resolved_model_revision=runtime_identity.get("resolved_model_revision"),
measured_prompts=[prompt],
)
num_gpus = init_kwargs.get("num_gpus", cfg.get("run_config", {}).get("required_gpus", 1))
hw_profile = hardware_profile(num_gpus=num_gpus)
sw_profile = software_profile()
env_metadata = environment_metadata(
hardware=hw_profile,
software=sw_profile,
)
return {
"recipe": recipe,
"recipe_fingerprint": recipe_fingerprint(recipe),
"hardware_profile": hw_profile,
"hardware_profile_id": hardware_profile_id(hw_profile),
"software_profile": sw_profile,
"software_profile_id": software_profile_id(sw_profile),
"environment_metadata": env_metadata,
"environment_fingerprint": environment_fingerprint(env_metadata),
**_benchmark_identity_fields(cfg),
}
# -- Test -------------------------------------------------------------------
def _run_benchmark(cfg):
@@ -264,6 +384,7 @@ def _run_benchmark(cfg):
model_path=model_info["model_path"],
**init_kwargs,
)
runtime_identity = _runtime_identity_from_generator(generator)
for i in range(num_warmup):
logger.info("Warmup run %d/%d", i + 1, num_warmup)
@@ -318,6 +439,7 @@ def _run_benchmark(cfg):
"dit_time_s": _avg_component(all_component_times, "dit_time_s"),
"vae_decode_time_s": _avg_component(all_component_times,
"vae_decode_time_s"),
**_build_identity_fields(cfg, init_kwargs, prompt, runtime_identity),
}
logger.info(