CafeClope's picture
download
raw
21.9 kB
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import json
import math
from pathlib import Path
import numpy as np
import pandas as pd
METHOD_ORDER = [
"HD-BasinFlow",
"ASHA Successive Halving",
"Optuna TPE",
"Random Search",
"Sobol Quasi-Random",
]
BASELINE_ORDER = [
"ASHA Successive Halving",
"Optuna TPE",
"Random Search",
"Sobol Quasi-Random",
]
WORKLOAD_LABELS = {
"fashion_mnist_tiny_cnn": "Fashion-MNIST CNN",
"cifar10_tiny_cnn": "CIFAR-10 CNN",
"sst2_distilbert": "SST-2 DistilBERT",
"imdb_distilbert": "IMDB DistilBERT",
"ag_news_tfidf_logreg": "AG News TF-IDF",
"tabular_credit_hgb": "Tabular Credit HGB",
}
METHOD_COLORS = {
"HD-BasinFlow": "#1b7f5a",
"ASHA Successive Halving": "#6d5dfc",
"Optuna TPE": "#d55e00",
"Random Search": "#4c78a8",
"Sobol Quasi-Random": "#7f7f7f",
}
def pct(value: float) -> str:
if pd.isna(value):
return "n/a"
return f"{100 * float(value):.1f}%"
def clean_label(value: str) -> str:
return WORKLOAD_LABELS.get(value, value.replace("_", " "))
def setup_matplotlib():
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
plt.rcParams.update(
{
"figure.dpi": 140,
"savefig.dpi": 300,
"font.size": 10,
"axes.titlesize": 12,
"axes.labelsize": 10,
"legend.fontsize": 8,
"xtick.labelsize": 8,
"ytick.labelsize": 8,
"axes.spines.top": False,
"axes.spines.right": False,
"pdf.fonttype": 42,
"ps.fonttype": 42,
}
)
return plt
def savefig(plt, outdir: Path, name: str) -> None:
plt.tight_layout()
plt.savefig(outdir / f"{name}.png")
plt.savefig(outdir / f"{name}.pdf")
plt.close()
def load_inputs(input_dir: Path) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
evals = pd.read_csv(input_dir / "combined" / "raw" / "evaluations.csv")
metrics = pd.read_csv(input_dir / "combined" / "processed" / "metrics.csv")
decisions = pd.read_csv(input_dir / "target_savings" / "target_savings_decisions.csv")
return evals, metrics, decisions
def normalize_evals(evals: pd.DataFrame) -> pd.DataFrame:
df = evals.copy()
for col in ["cumulative_cost", "best_loss_so_far", "loss", "walltime", "iteration", "seed"]:
if col in df:
df[col] = pd.to_numeric(df[col], errors="coerce")
df["method_label"] = np.where(df["method"].eq("HD-BasinFlow"), "HD-BasinFlow", df["method"])
df = df[df["method_label"].isin(METHOD_ORDER)].copy()
df["workload_label"] = df["experiment"].map(clean_label)
return df
def normalize_metrics(metrics: pd.DataFrame) -> pd.DataFrame:
df = metrics.copy()
for col in ["median_final_best_loss", "median_walltime", "median_cost", "runs"]:
if col in df:
df[col] = pd.to_numeric(df[col], errors="coerce")
df["method_label"] = np.where(df["method"].eq("HD-BasinFlow"), "HD-BasinFlow", df["method"])
df = df[df["method_label"].isin(METHOD_ORDER)].copy()
df["workload_label"] = df["experiment"].map(clean_label)
return df
def figure_pass_rate(plt, decisions: pd.DataFrame, outdir: Path) -> str:
summary = (
decisions.groupby("baseline", as_index=False)
.agg(comparisons=("baseline", "size"), passes=("decision", lambda s: int((s == "pass").sum())))
)
summary["pass_rate"] = summary["passes"] / summary["comparisons"]
summary["baseline"] = pd.Categorical(summary["baseline"], BASELINE_ORDER, ordered=True)
summary = summary.sort_values("baseline")
fig, ax = plt.subplots(figsize=(6.6, 3.8))
bars = ax.bar(summary["baseline"].astype(str), summary["pass_rate"] * 100, color="#1b7f5a")
ax.set_ylim(0, 105)
ax.set_ylabel("pass rate (%)")
ax.set_title("Target-savings pass rate by competitor")
ax.set_xticks(np.arange(len(summary)))
ax.set_xticklabels(summary["baseline"].astype(str), rotation=25, ha="right")
for bar, row in zip(bars, summary.itertuples(index=False)):
ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 2, f"{row.passes}/{row.comparisons}", ha="center", va="bottom")
savefig(plt, outdir, "fig01_pass_rate_by_baseline")
return "fig01_pass_rate_by_baseline"
def figure_savings_by_baseline(plt, decisions: pd.DataFrame, outdir: Path) -> str:
summary = (
decisions.groupby("baseline", as_index=False)
.agg(
eval_savings=("evaluation_savings_fraction", "median"),
wall_savings=("walltime_savings_fraction", "median"),
)
)
summary["baseline"] = pd.Categorical(summary["baseline"], BASELINE_ORDER, ordered=True)
summary = summary.sort_values("baseline")
x = np.arange(len(summary))
width = 0.36
fig, ax = plt.subplots(figsize=(7.2, 4.0))
ax.bar(x - width / 2, summary["eval_savings"] * 100, width, label="evaluations", color="#1b7f5a")
ax.bar(x + width / 2, summary["wall_savings"] * 100, width, label="wall time", color="#4c78a8")
ax.axhspan(15, 30, color="#e6f2ec", zorder=0, label="15-30% target band")
ax.set_ylabel("median savings (%)")
ax.set_title("Median compute savings at matched quality target")
ax.set_xticks(x)
ax.set_xticklabels(summary["baseline"].astype(str), rotation=25, ha="right")
ax.legend(frameon=False)
savefig(plt, outdir, "fig02_median_savings_by_baseline")
return "fig02_median_savings_by_baseline"
def figure_quality_savings_scatter(plt, decisions: pd.DataFrame, outdir: Path) -> str:
fig, ax = plt.subplots(figsize=(7.0, 4.4))
for baseline in BASELINE_ORDER:
g = decisions[decisions["baseline"].eq(baseline)]
if g.empty:
continue
ax.scatter(
g["evaluation_savings_fraction"] * 100,
g["loss_delta_pct"] * 100,
s=70,
label=baseline,
alpha=0.86,
edgecolor="white",
linewidth=0.7,
)
ax.axhline(1.0, color="#8f2f2f", linestyle="--", linewidth=1.2, label="1% quality tolerance")
ax.axvline(20.0, color="#444444", linestyle=":", linewidth=1.2, label="20% savings target")
ax.set_xlabel("evaluation savings (%)")
ax.set_ylabel("HD loss delta vs baseline (%)")
ax.set_title("Quality-compute tradeoff across workloads")
ax.legend(frameon=False, ncols=2)
savefig(plt, outdir, "fig03_quality_vs_compute_savings")
return "fig03_quality_vs_compute_savings"
def figure_hd_vs_baseline_savings(plt, decisions: pd.DataFrame, outdir: Path) -> str:
data = decisions.copy()
data["comparison"] = data["experiment"].map(clean_label) + "\nvs " + data["baseline"]
data = data.sort_values("evaluation_savings_fraction", ascending=True)
colors = np.where(data["decision"].eq("pass"), "#1b7f5a", "#8f2f2f")
fig, ax = plt.subplots(figsize=(8.6, 8.2))
y = np.arange(len(data))
ax.barh(y, data["evaluation_savings_fraction"] * 100, color=colors)
ax.axvline(20, color="#222222", linestyle="--", linewidth=1.2, label="20% savings target")
ax.axvspan(15, 30, color="#e6f2ec", zorder=0, label="15-30% target band")
ax.set_yticks(y)
ax.set_yticklabels(data["comparison"], fontsize=7)
ax.set_xlabel("HD-BasinFlow evaluation savings vs baseline (%)")
ax.set_title("Direct comparison: HD-BasinFlow compute savings vs each baseline")
ax.legend(frameon=False, loc="lower right")
for idx, row in enumerate(data.itertuples(index=False)):
val = row.evaluation_savings_fraction * 100
ax.text(val + 1, idx, "pass" if row.decision == "pass" else "fail", va="center", fontsize=7)
savefig(plt, outdir, "fig09_hd_vs_baseline_savings")
return "fig09_hd_vs_baseline_savings"
def figure_hd_vs_baseline_quality_delta(plt, decisions: pd.DataFrame, outdir: Path) -> str:
data = decisions.copy()
data["comparison"] = data["experiment"].map(clean_label) + "\nvs " + data["baseline"]
data = data.sort_values("loss_delta_pct", ascending=True)
colors = np.where(data["quality_pass"], "#1b7f5a", "#8f2f2f")
fig, ax = plt.subplots(figsize=(8.6, 8.2))
y = np.arange(len(data))
ax.barh(y, data["loss_delta_pct"] * 100, color=colors)
ax.axvline(0, color="#222222", linewidth=1.0)
ax.axvline(1, color="#8f2f2f", linestyle="--", linewidth=1.2, label="1% quality tolerance")
ax.set_yticks(y)
ax.set_yticklabels(data["comparison"], fontsize=7)
ax.set_xlabel("HD-BasinFlow median loss delta vs baseline (%)")
ax.set_title("Direct comparison: HD-BasinFlow quality delta vs each baseline")
ax.legend(frameon=False, loc="lower right")
savefig(plt, outdir, "fig10_hd_vs_baseline_quality_delta")
return "fig10_hd_vs_baseline_quality_delta"
def _heatmap(plt, data: pd.DataFrame, value_col: str, title: str, cbar_label: str, outdir: Path, name: str) -> str:
pivot = data.pivot(index="experiment", columns="baseline", values=value_col)
pivot = pivot.reindex(sorted(pivot.index, key=lambda v: clean_label(v))).reindex(columns=BASELINE_ORDER)
values = pivot.to_numpy(dtype=float)
fig, ax = plt.subplots(figsize=(8.2, 4.6))
vmax = np.nanmax(np.abs(values)) if np.isfinite(values).any() else 1.0
if value_col == "loss_delta_pct":
vmax = max(vmax, 0.03)
im = ax.imshow(values * 100, cmap="RdYlGn_r", vmin=-vmax * 100, vmax=vmax * 100, aspect="auto")
else:
im = ax.imshow(values * 100, cmap="YlGn", vmin=0, vmax=max(80, np.nanmax(values * 100)), aspect="auto")
ax.set_xticks(np.arange(len(pivot.columns)))
ax.set_xticklabels(pivot.columns, rotation=25, ha="right")
ax.set_yticks(np.arange(len(pivot.index)))
ax.set_yticklabels([clean_label(v) for v in pivot.index])
ax.set_title(title)
for i in range(values.shape[0]):
for j in range(values.shape[1]):
if np.isfinite(values[i, j]):
ax.text(j, i, f"{values[i, j] * 100:.1f}", ha="center", va="center", color="black", fontsize=8)
cbar = fig.colorbar(im, ax=ax, shrink=0.9)
cbar.set_label(cbar_label)
savefig(plt, outdir, name)
return name
def figure_heatmaps(plt, decisions: pd.DataFrame, outdir: Path) -> list[str]:
names = []
names.append(
_heatmap(
plt,
decisions,
"evaluation_savings_fraction",
"Evaluation savings by workload and competitor",
"savings (%)",
outdir,
"fig04_heatmap_evaluation_savings",
)
)
names.append(
_heatmap(
plt,
decisions,
"loss_delta_pct",
"Median loss delta by workload and competitor",
"loss delta (%)",
outdir,
"fig05_heatmap_quality_delta",
)
)
return names
def figure_final_loss_by_workload(plt, metrics: pd.DataFrame, outdir: Path) -> list[str]:
names = []
use = metrics[metrics["method_label"].isin(METHOD_ORDER)].copy()
for experiment, g in use.groupby("experiment"):
g = g.copy()
g["method_label"] = pd.Categorical(g["method_label"], METHOD_ORDER, ordered=True)
g = g.sort_values("method_label")
fig, ax = plt.subplots(figsize=(6.8, 3.7))
colors = [METHOD_COLORS.get(m, "#777777") for m in g["method_label"].astype(str)]
ax.bar(g["method_label"].astype(str), g["median_final_best_loss"], color=colors)
ax.set_title(f"Final validation loss: {clean_label(experiment)}")
ax.set_ylabel("median final best loss")
ax.set_xticks(np.arange(len(g)))
ax.set_xticklabels(g["method_label"].astype(str), rotation=25, ha="right")
name = f"fig_loss_{experiment}"
savefig(plt, outdir, name)
names.append(name)
return names
def figure_convergence_curves(plt, evals: pd.DataFrame, outdir: Path) -> list[str]:
names = []
trend = (
evals.groupby(["experiment", "method_label", "cumulative_cost"], as_index=False)["best_loss_so_far"]
.median()
.sort_values(["experiment", "method_label", "cumulative_cost"])
)
for experiment, exp_df in trend.groupby("experiment"):
fig, ax = plt.subplots(figsize=(7.0, 4.2))
for method in METHOD_ORDER:
g = exp_df[exp_df["method_label"].eq(method)]
if g.empty:
continue
ax.plot(
g["cumulative_cost"],
g["best_loss_so_far"],
label=method,
linewidth=2.0 if method == "HD-BasinFlow" else 1.4,
color=METHOD_COLORS.get(method),
)
ax.set_title(f"Optimization curve: {clean_label(experiment)}")
ax.set_xlabel("evaluations")
ax.set_ylabel("median best loss so far")
ax.legend(frameon=False)
name = f"fig_curve_{experiment}"
savefig(plt, outdir, name)
names.append(name)
return names
def figure_active_dimension(plt, evals: pd.DataFrame, outdir: Path) -> str | None:
hd = evals[(evals["method_label"].eq("HD-BasinFlow")) & evals["active_dim"].notna()].copy()
if hd.empty:
return None
hd["active_dim"] = pd.to_numeric(hd["active_dim"], errors="coerce")
trend = hd.groupby(["experiment", "cumulative_cost"], as_index=False)["active_dim"].median()
fig, ax = plt.subplots(figsize=(7.0, 4.1))
for experiment, g in trend.groupby("experiment"):
ax.plot(g["cumulative_cost"], g["active_dim"], label=clean_label(experiment), linewidth=1.5)
ax.set_title("HD-BasinFlow active subspace dimension over search")
ax.set_xlabel("evaluations")
ax.set_ylabel("median estimated active dimension")
ax.legend(frameon=False, ncols=2)
savefig(plt, outdir, "fig06_active_dimension_over_time")
return "fig06_active_dimension_over_time"
def figure_allocation_sources(plt, evals: pd.DataFrame, outdir: Path) -> str | None:
hd = evals[evals["method_label"].eq("HD-BasinFlow")].copy()
if hd.empty or "source" not in hd:
return None
table = pd.crosstab(hd["experiment"].map(clean_label), hd["source"], normalize="index") * 100
keep = [c for c in ["initial", "global", "edge_flow", "basin", "repair"] if c in table.columns]
table = table[keep]
fig, ax = plt.subplots(figsize=(8.0, 4.5))
bottom = np.zeros(len(table))
colors = ["#4c78a8", "#b0b0b0", "#1b7f5a", "#72b7b2", "#d55e00"]
x = np.arange(len(table))
for idx, col in enumerate(table.columns):
vals = table[col].to_numpy()
ax.bar(x, vals, bottom=bottom, label=col, color=colors[idx % len(colors)])
bottom += vals
ax.set_ylabel("share of HD evaluations (%)")
ax.set_title("HD-BasinFlow allocation source mix")
ax.set_xticks(x)
ax.set_xticklabels(table.index, rotation=25, ha="right")
ax.legend(frameon=False, ncols=len(table.columns))
savefig(plt, outdir, "fig07_allocation_source_mix")
return "fig07_allocation_source_mix"
def figure_train_val_curves(plt, evals: pd.DataFrame, outdir: Path) -> str | None:
rows = []
epoch_workloads = {"fashion_mnist_tiny_cnn", "cifar10_tiny_cnn", "sst2_distilbert", "imdb_distilbert"}
for row in evals.itertuples(index=False):
method = getattr(row, "method_label")
if method != "HD-BasinFlow":
continue
experiment = getattr(row, "experiment")
if experiment not in epoch_workloads:
continue
val_json = getattr(row, "val_curve_json", None)
train_json = getattr(row, "train_curve_json", None)
for kind, blob in [("train", train_json), ("validation", val_json)]:
if not isinstance(blob, str) or not blob.strip():
continue
try:
points = json.loads(blob)
except json.JSONDecodeError:
continue
for point in points:
rows.append(
{
"experiment": experiment,
"curve": kind,
"epoch": point.get("epoch"),
"loss": point.get("loss"),
}
)
curves = pd.DataFrame(rows)
if curves.empty:
return None
curves["epoch"] = pd.to_numeric(curves["epoch"], errors="coerce")
curves["loss"] = pd.to_numeric(curves["loss"], errors="coerce")
curves = curves.dropna()
curves = curves[(curves["epoch"] >= 0) & (curves["epoch"] <= 10)]
summary = curves.groupby(["experiment", "curve", "epoch"], as_index=False)["loss"].median()
fig, ax = plt.subplots(figsize=(7.3, 4.3))
for (experiment, curve), g in summary.groupby(["experiment", "curve"]):
linestyle = "-" if curve == "validation" else "--"
ax.plot(g["epoch"], g["loss"], label=f"{clean_label(experiment)} {curve}", linestyle=linestyle, linewidth=1.4)
ax.set_title("HD-BasinFlow selected training/validation curves")
ax.set_xlabel("epoch")
ax.set_ylabel("median loss")
ax.legend(frameon=False, fontsize=7, ncols=2)
savefig(plt, outdir, "fig08_hd_train_validation_curves")
return "fig08_hd_train_validation_curves"
def write_index(outdir: Path, generated: list[str], decisions: pd.DataFrame) -> None:
lines = [
"# HD-BasinFlow Paper Figure Index",
"",
"All figures are generated from `runs/commercial_benchmark_a100`, the fresh A100 benchmark suite.",
"Each figure is saved as both `.png` and `.pdf` unless noted.",
"",
"## Headline Evidence",
"",
f"- Reviewable comparisons: {len(decisions)}",
f"- Operating-point passes: {int((decisions['decision'] == 'pass').sum())}",
f"- Median evaluation savings: {pct(decisions['evaluation_savings_fraction'].median())}",
f"- Median walltime savings: {pct(decisions['walltime_savings_fraction'].median())}",
"",
"## Figures",
"",
]
captions = {
"fig01_pass_rate_by_baseline": "Pass rate by competitor under the 1% quality / 20% savings operating-point rule.",
"fig02_median_savings_by_baseline": "Median evaluation and walltime savings by competitor; shaded region marks the conservative 15-30% commercial band.",
"fig03_quality_vs_compute_savings": "Quality-compute tradeoff for every workload-baseline pair.",
"fig04_heatmap_evaluation_savings": "Evaluation savings heatmap across workloads and competitors.",
"fig05_heatmap_quality_delta": "Median loss delta heatmap across workloads and competitors.",
"fig06_active_dimension_over_time": "HD-BasinFlow active subspace estimates during the search.",
"fig07_allocation_source_mix": "How HD-BasinFlow allocated evaluations across initialization, global search, edge flow, basin continuation, and repairs.",
"fig08_hd_train_validation_curves": "Median train/validation curves for HD-BasinFlow-selected configurations where epoch curves were logged.",
"fig09_hd_vs_baseline_savings": "Direct per-workload comparison of HD-BasinFlow evaluation savings against each baseline.",
"fig10_hd_vs_baseline_quality_delta": "Direct per-workload comparison of HD-BasinFlow median loss delta against each baseline.",
}
for name in generated:
caption = captions.get(name, f"Paper figure generated from benchmark data: `{name}`.")
lines.append(f"- `{name}.pdf` / `{name}.png`: {caption}")
lines.extend(
[
"",
"## Suggested Paper Placement",
"",
"- Main paper: fig01, fig02, fig03, fig04, fig05.",
"- Method/results appendix: convergence curves, final-loss bars, active-dimension, allocation-source, and train/validation figures.",
"- Avoid overclaiming: the evidence supports compute savings at matched validation quality, not global optimality.",
]
)
(outdir / "FIGURE_INDEX.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
def main() -> int:
parser = argparse.ArgumentParser(description="Build paper-ready HD-BasinFlow figures.")
parser.add_argument("--input-dir", type=Path, default=Path("runs/commercial_benchmark_a100"))
parser.add_argument("--outdir", type=Path, default=Path("runs/paper_figures"))
args = parser.parse_args()
args.outdir.mkdir(parents=True, exist_ok=True)
plt = setup_matplotlib()
evals, metrics, decisions = load_inputs(args.input_dir)
evals = normalize_evals(evals)
metrics = normalize_metrics(metrics)
generated: list[str] = []
generated.append(figure_pass_rate(plt, decisions, args.outdir))
generated.append(figure_savings_by_baseline(plt, decisions, args.outdir))
generated.append(figure_quality_savings_scatter(plt, decisions, args.outdir))
generated.append(figure_hd_vs_baseline_savings(plt, decisions, args.outdir))
generated.append(figure_hd_vs_baseline_quality_delta(plt, decisions, args.outdir))
generated.extend(figure_heatmaps(plt, decisions, args.outdir))
active = figure_active_dimension(plt, evals, args.outdir)
if active:
generated.append(active)
allocation = figure_allocation_sources(plt, evals, args.outdir)
if allocation:
generated.append(allocation)
train_val = figure_train_val_curves(plt, evals, args.outdir)
if train_val:
generated.append(train_val)
generated.extend(figure_convergence_curves(plt, evals, args.outdir))
generated.extend(figure_final_loss_by_workload(plt, metrics, args.outdir))
decisions.to_csv(args.outdir / "paper_decision_table.csv", index=False)
metrics.to_csv(args.outdir / "paper_metrics_table.csv", index=False)
write_index(args.outdir, generated, decisions)
print(args.outdir)
print(f"generated_figures={len(generated)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())

Xet Storage Details

Size:
21.9 kB
·
Xet hash:
a16f01720c22c42f35944a3c4bea0742b806c54332104c0dd32fd1e3291945b1

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.