034c197260
Validate Operations / validate-operations (push) Failing after 3h10m55s
add plot generation scripts adjust validation tools add up-to-date results
515 lines
25 KiB
Python
Executable File
515 lines
25 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""Generate publication-ready Raptor vs Pimcomp comparison charts.
|
||
|
||
Usage:
|
||
.venv/bin/python validation/tools/pim/pimcomp/compare/generate_plots.py
|
||
|
||
Dependencies: matplotlib (no pandas or seaborn required).
|
||
By default, reads results_comparison.csv beside this script and writes plots/
|
||
beside it, with outputs grouped in png/ and svg/ under the output directory.
|
||
|
||
YOLO models without Pimcomp measurements get separate Raptor-only charts.
|
||
Comparisons use vertical bars for ratios, diverging advantages, and absolute
|
||
values, with compiler colors and patterns for the Raptor pipeline stages.
|
||
Throughput-mode latency is the output interval, a bottleneck-stage proxy,
|
||
not end-to-end inference latency. Separate throughput plots use inferences/s.
|
||
Energy is reported per inference; power is averaged over the simulation.
|
||
Numerical results whose compiler did not pass functional validation have a red
|
||
status following their value label; missing Pimcomp
|
||
measurements appear as 'N/A'. The fixed compiler palette is shared by
|
||
all metrics. Relative plots use Pimcomp / Raptor for latency, power and energy,
|
||
and Raptor / Pimcomp for throughput, so >1 favors Raptor. Diverging plots put the winner's
|
||
factor above (Raptor) or below (Pimcomp) parity, with 1x at the zero line.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import csv
|
||
import math
|
||
import sys
|
||
from dataclasses import dataclass
|
||
from itertools import groupby
|
||
from pathlib import Path
|
||
|
||
import matplotlib
|
||
matplotlib.use("Agg")
|
||
import matplotlib.pyplot as plt
|
||
from matplotlib.patches import Patch, Rectangle
|
||
from matplotlib.lines import Line2D
|
||
from matplotlib.ticker import FuncFormatter, MultipleLocator
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||
from plot_style import COLORS, HATCH_COLOR, TEXT_COLOR, configure_style, fit_value_labels, save_figure, value_label
|
||
|
||
|
||
COMPILER_COLORS = {"raptor": COLORS[0], "pimcomp": COLORS[2]}
|
||
STAGE_PATTERNS = {1: "", 2: "..", 4: "//", 8: "\\\\"}
|
||
ARCHITECTURES = ("arch-a", "arch-b")
|
||
MODELS = ("vgg8", "resnet18", "resnet34", "googlenet", "yolo11n")
|
||
CONFIGURATIONS = (("latency", 1), ("throughput", 2), ("throughput", 4), ("throughput", 8))
|
||
LINE_COLOR = "#A8B2C0"
|
||
VALIDATION_STYLES = {
|
||
"PASS": ("#168C8B", "white", ""),
|
||
"FAIL": ("#D18D32", HATCH_COLOR, ""),
|
||
"SKIPPED": ("#E7DFF0", TEXT_COLOR, ""),
|
||
"NA": ("#E8EDF1", TEXT_COLOR, ""),
|
||
}
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class Result:
|
||
arch: str
|
||
model: str
|
||
mode: str
|
||
pipeline: int
|
||
pimcomp_schedule: str
|
||
raptor_validation: str
|
||
pimcomp_validation: str
|
||
latency_speedup: float
|
||
power: float
|
||
energy: float
|
||
raptor_latency: float
|
||
pimcomp_latency: float
|
||
raptor_throughput: float
|
||
pimcomp_throughput: float
|
||
raptor_power: float
|
||
pimcomp_power: float
|
||
raptor_energy: float
|
||
pimcomp_energy: float
|
||
|
||
@property
|
||
def comparison_passed(self) -> bool:
|
||
return self.raptor_validation == self.pimcomp_validation == "PASS"
|
||
|
||
@property
|
||
def throughput_speedup(self) -> float:
|
||
return quotient(self.raptor_throughput, self.pimcomp_throughput)
|
||
|
||
|
||
def positive_float(text: str | None) -> float:
|
||
try:
|
||
value = float(text)
|
||
except (TypeError, ValueError):
|
||
return math.nan
|
||
return value if math.isfinite(value) and value > 0 else math.nan
|
||
|
||
|
||
def quotient(numerator: float, denominator: float) -> float:
|
||
if not (math.isfinite(numerator) and math.isfinite(denominator)):
|
||
return math.nan
|
||
return numerator / denominator
|
||
|
||
|
||
def validation_status(text: str) -> str:
|
||
status = text.strip().upper()
|
||
if status not in VALIDATION_STYLES:
|
||
raise ValueError(f"Unknown functional-validation status: {text!r}")
|
||
return status
|
||
|
||
|
||
def load_results(path: Path) -> list[Result]:
|
||
with path.open(newline="", encoding="utf-8-sig") as handle:
|
||
source = list(csv.DictReader(handle))
|
||
rows = []
|
||
expected = {(arch, model, mode, stage)
|
||
for arch in ARCHITECTURES for model in MODELS
|
||
for mode, stage in CONFIGURATIONS}
|
||
seen = {}
|
||
for line, row in enumerate(source, 2):
|
||
try:
|
||
arch, model, mode = (row[field].strip().lower() for field in ("arch", "model", "mode"))
|
||
stage = int(row["raptor_pipeline"])
|
||
except (KeyError, AttributeError, TypeError, ValueError) as exc:
|
||
raise ValueError(f"Invalid configuration key at CSV row {line}: {exc}") from exc
|
||
key = arch, model, mode, stage
|
||
if key not in expected:
|
||
raise ValueError(f"Unexpected configuration at CSV row {line}: {key}")
|
||
if key in seen:
|
||
raise ValueError(f"Duplicate configuration {key} at CSV rows {seen[key]} and {line}")
|
||
seen[key] = line
|
||
schedule = row["pimcomp_pipeline"].strip().lower()
|
||
if schedule != ("element" if mode == "latency" else "batch"):
|
||
raise ValueError(f"{key}: incompatible Pimcomp schedule {schedule!r}")
|
||
rows.append(Result(
|
||
arch=arch,
|
||
model=model,
|
||
mode=mode,
|
||
pipeline=stage,
|
||
pimcomp_schedule=schedule,
|
||
raptor_validation=validation_status(row["raptor_functional_validation"]),
|
||
pimcomp_validation=validation_status(row["pimcomp_functional_validation"]),
|
||
latency_speedup=quotient(positive_float(row["pimcomp_latency_ms"]),
|
||
positive_float(row["raptor_latency_ms"])),
|
||
power=quotient(positive_float(row["pimcomp_power_mw"]),
|
||
positive_float(row["raptor_power_mw"])),
|
||
energy=quotient(positive_float(row["pimcomp_energy_pj"]),
|
||
positive_float(row["raptor_energy_pj"])),
|
||
raptor_latency=positive_float(row["raptor_latency_ms"]),
|
||
pimcomp_latency=positive_float(row["pimcomp_latency_ms"]),
|
||
raptor_throughput=positive_float(row["raptor_throughput_samples_s"]),
|
||
pimcomp_throughput=positive_float(row["pimcomp_throughput_samples_s"]),
|
||
raptor_power=positive_float(row["raptor_power_mw"]),
|
||
pimcomp_power=positive_float(row["pimcomp_power_mw"]),
|
||
raptor_energy=positive_float(row["raptor_energy_pj"]),
|
||
pimcomp_energy=positive_float(row["pimcomp_energy_pj"]),
|
||
))
|
||
missing = expected - seen.keys()
|
||
if missing:
|
||
raise ValueError(f"Missing {len(missing)} expected configurations. Examples: {sorted(missing)[:5]}")
|
||
rows.sort(key=lambda r: (ARCHITECTURES.index(r.arch), MODELS.index(r.model), r.pipeline))
|
||
absolute_columns(rows)
|
||
return rows
|
||
|
||
|
||
def advantage_compiler(ratio: float) -> str | None:
|
||
if ratio > 1:
|
||
return "raptor"
|
||
if ratio < 1:
|
||
return "pimcomp"
|
||
return None
|
||
|
||
|
||
def figure_legend(fig, results: list[Result], field: str,
|
||
absolute=False, raptor_only=False):
|
||
compilers = ("raptor",) if raptor_only else ("raptor", "pimcomp")
|
||
if absolute:
|
||
measurements = [(row, compiler, getattr(row, f"{compiler}_{field}"))
|
||
for row, compiler in absolute_columns(results, raptor_only)]
|
||
else:
|
||
measurements = [(row, None, getattr(row, field)) for row in results]
|
||
available = [(row, compiler, value) for row, compiler, value in measurements
|
||
if math.isfinite(value)]
|
||
stages = {row.pipeline for row, compiler, _ in available if compiler != "pimcomp"}
|
||
handles = []
|
||
visible_compilers = {compiler if absolute else advantage_compiler(value)
|
||
for _, compiler, value in available} - {None}
|
||
if len(visible_compilers) > 1:
|
||
for compiler in compilers:
|
||
handles.append(Patch(facecolor=COMPILER_COLORS[compiler],
|
||
label=compiler.capitalize() + ("" if absolute else " advantage")))
|
||
handles += [Patch(facecolor="white", edgecolor=TEXT_COLOR, hatch=STAGE_PATTERNS[depth],
|
||
label=f"{depth} stage" + (" (latency)" if depth == 1 else "s"))
|
||
for depth in sorted(stages) if stages != {1}]
|
||
styles = []
|
||
if len(available) < len(measurements):
|
||
styles.append(Patch(facecolor="white", edgecolor="#B2BAC5",
|
||
label="N/A: missing measurement"))
|
||
handles += styles
|
||
if handles:
|
||
fig.legend(handles=handles, loc="outside upper center", ncol=(len(handles) + 1) // 2,
|
||
frameon=False, fontsize=9.5, handlelength=1.7, columnspacing=1.5,
|
||
handletextpad=0.6, labelspacing=0.4)
|
||
|
||
|
||
def absolute_columns(results: list[Result], raptor_only: bool = False) -> list[tuple[Result, str]]:
|
||
columns = []
|
||
for (arch, model), group in groupby(results, key=lambda row: (row.arch, row.model)):
|
||
rows = sorted(group, key=lambda row: row.pipeline)
|
||
for row in rows:
|
||
columns.append((row, "raptor"))
|
||
if row.mode == "latency" and not raptor_only:
|
||
columns.append((row, "pimcomp"))
|
||
throughput = [row for row in rows if row.mode == "throughput"]
|
||
if throughput and not raptor_only:
|
||
signatures = {
|
||
(row.pimcomp_schedule, row.pimcomp_validation,
|
||
*(None if math.isnan(value) else value for value in (
|
||
row.pimcomp_latency, row.pimcomp_throughput, row.pimcomp_power, row.pimcomp_energy)))
|
||
for row in throughput
|
||
}
|
||
if len(signatures) != 1:
|
||
raise ValueError(f"{arch}/{model}: inconsistent shared Pimcomp batch results across pipelines")
|
||
columns.append((throughput[0], "pimcomp"))
|
||
return columns
|
||
|
||
|
||
def label_groups(ax, results: list[Result], positions: list[float] | None = None) -> None:
|
||
if positions is None:
|
||
positions = list(range(len(results)))
|
||
centers, labels = [], []
|
||
start = 0
|
||
for index, (model, rows) in enumerate(groupby(results, key=lambda row: row.model)):
|
||
end = start + len(list(rows))
|
||
centers.append((positions[start] + positions[end - 1]) / 2)
|
||
labels.append(model.upper())
|
||
left = ax.get_xlim()[0] if start == 0 else (positions[start - 1] + positions[start]) / 2
|
||
right = ax.get_xlim()[1] if end == len(results) else (positions[end - 1] + positions[end]) / 2
|
||
if index % 2 == 0:
|
||
ax.axvspan(left, right, facecolor="#F7F9FC", zorder=0)
|
||
if index > 0:
|
||
ax.axvline(left, color="#DDE3EB", linewidth=0.9)
|
||
start = end
|
||
ax.set_xticks(centers, labels, fontsize=9.5)
|
||
|
||
|
||
def diverging_ratio(ratio: float) -> float:
|
||
return ratio - 1 if ratio >= 1 else 1 - 1 / ratio
|
||
|
||
|
||
def draw_validation(results: list[Result], output_dir: Path) -> None:
|
||
columns = absolute_columns(results)
|
||
configurations = list(dict.fromkeys((row.mode, row.pipeline if compiler == "raptor" else row.pimcomp_schedule,
|
||
compiler) for row, compiler in columns))
|
||
positions = []
|
||
for index, (mode, _, _) in enumerate(configurations):
|
||
gap = 0.4 if index and mode != configurations[index - 1][0] else 0
|
||
positions.append(positions[-1] + 1 + gap if index else 0)
|
||
configuration_x = dict(zip(configurations, positions))
|
||
headers = [f"Raptor\n{stage} stages" if mode == "throughput" and compiler == "raptor"
|
||
else "Pimcomp\nbatch" if mode == "throughput" else compiler.capitalize()
|
||
for mode, stage, compiler in configurations]
|
||
fig, axes = plt.subplots(1, 2, figsize=(14, 3.8), layout="constrained")
|
||
for ax, arch in zip(axes, ARCHITECTURES):
|
||
ax.set_xlim(positions[0] - 0.55, positions[-1] + 0.55)
|
||
ax.set_ylim(len(MODELS) - 0.5, -0.5)
|
||
centers, modes = [], []
|
||
for mode, group in groupby(zip(configurations, positions), key=lambda item: item[0][0]):
|
||
group_positions = [x for _, x in group]
|
||
centers.append((group_positions[0] + group_positions[-1]) / 2)
|
||
modes.append(mode.capitalize())
|
||
ax.set_xticks(centers, modes, fontsize=10)
|
||
ax.set_xticks(positions, headers, minor=True)
|
||
ax.tick_params(axis="x", which="minor", top=True, labeltop=True,
|
||
bottom=False, labelbottom=False, length=0, pad=6, labelsize=9)
|
||
ax.set_yticks(range(len(MODELS)), [model.upper() for model in MODELS], fontsize=9)
|
||
ax.tick_params(axis="both", length=0, pad=6)
|
||
architecture = "ARCHITECTURE A" if arch == "arch-a" else "ARCHITECTURE B"
|
||
ax.set_xlabel(architecture, fontsize=11, weight="bold", labelpad=8)
|
||
for row, compiler in columns:
|
||
if row.arch != arch:
|
||
continue
|
||
key = (row.mode, row.pipeline if compiler == "raptor" else row.pimcomp_schedule, compiler)
|
||
x, y = configuration_x[key], MODELS.index(row.model)
|
||
status = getattr(row, f"{compiler}_validation")
|
||
color, text_color, _ = VALIDATION_STYLES[status]
|
||
ax.add_patch(Rectangle((x - 0.5, y - 0.42), 1, 0.84,
|
||
facecolor=color, edgecolor="#657081", linewidth=0.5))
|
||
ax.text(x, y, "N/A" if status == "NA" else status,
|
||
color=text_color, ha="center", va="center", fontsize=9)
|
||
save_figure(fig, "validation_status", output_dir)
|
||
|
||
|
||
def draw_metric(results: list[Result], field: str, ylabel: str,
|
||
basename: str, output_dir: Path, diverging: bool = False) -> None:
|
||
data = [diverging_ratio(getattr(row, field)) if diverging else getattr(row, field)
|
||
for row in results]
|
||
available = [v for v in data if math.isfinite(v)]
|
||
upper = max(0, max(available, default=1.0))
|
||
lower = min(0, min(available, default=0)) if diverging else 0
|
||
padding = max(upper - lower, 1.0) * 0.3
|
||
y_max = math.ceil((upper + padding) * 2) / 2
|
||
y_min = math.floor((lower - padding) * 2) / 2 if diverging else 0
|
||
y_interval = 1.0 if y_max - y_min > 4.5 else 0.5
|
||
|
||
fig, axes = plt.subplots(1, 2, sharey=True, figsize=(14, 5.2),
|
||
layout="constrained")
|
||
figure_legend(fig, results, field)
|
||
|
||
for ax, arch in zip(axes, ARCHITECTURES):
|
||
selected = [row for row in results if row.arch == arch]
|
||
ax.set_xlabel("ARCHITECTURE A" if arch == "arch-a" else "ARCHITECTURE B",
|
||
fontsize=11, weight="bold", labelpad=8)
|
||
ax.set_axisbelow(True)
|
||
ax.set_ylim(y_min, y_max)
|
||
ax.set_xlim(-0.5, len(selected) - 0.5)
|
||
label_groups(ax, selected)
|
||
ax.tick_params(axis="y", labelleft=True, pad=5, length=0)
|
||
ax.tick_params(axis="x", labelsize=9.5, length=0, pad=6)
|
||
ax.yaxis.set_major_locator(MultipleLocator(y_interval))
|
||
if diverging:
|
||
ax.yaxis.set_major_formatter(FuncFormatter(lambda value, _: f"{abs(value) + 1:g}×"))
|
||
ax.grid(axis="y", color="#E6EAF0", linewidth=0.8)
|
||
ax.axhline(0 if diverging else 1, color="#536478", linewidth=1.2,
|
||
linestyle="--", zorder=3)
|
||
ax.set_ylabel(ylabel, fontsize=10.5, labelpad=6)
|
||
|
||
for position, row in enumerate(selected):
|
||
ratio = getattr(row, field)
|
||
if not math.isfinite(ratio):
|
||
ax.text(position, (y_max - y_min) * 0.02, "N/A", ha="center", va="bottom",
|
||
fontsize=9.5, color="#85909F", fontstyle="italic")
|
||
continue
|
||
compiler = advantage_compiler(ratio)
|
||
statuses = [status for status in (row.raptor_validation, row.pimcomp_validation) if status != "PASS"]
|
||
status = "FAIL" if "FAIL" in statuses else next(iter(statuses), "PASS")
|
||
pattern = STAGE_PATTERNS[row.pipeline]
|
||
measurement = diverging_ratio(ratio) if diverging else ratio
|
||
ax.bar(position, measurement, width=0.72,
|
||
color=COMPILER_COLORS.get(compiler, "#657081"),
|
||
hatch=pattern,
|
||
edgecolor=TEXT_COLOR if pattern else "none",
|
||
linewidth=0.7 if pattern else 0.0, zorder=2)
|
||
direction = -1 if measurement < 0 else 1
|
||
factor = max(ratio, 1 / ratio) if diverging else ratio
|
||
value_label(ax, position, measurement + direction * (y_max - y_min) * 0.012,
|
||
f"{factor:.2f}×", status, direction, fontsize=9.1)
|
||
|
||
fit_value_labels(fig)
|
||
save_figure(fig, basename, output_dir)
|
||
|
||
|
||
def draw_absolute(results: list[Result], field: str, ylabel: str,
|
||
basename: str, output_dir: Path, scale: float = 1.0,
|
||
raptor_only: bool = False) -> None:
|
||
columns = absolute_columns(results, raptor_only)
|
||
available = [getattr(row, f"{compiler}_{field}") * scale
|
||
for row, compiler in columns
|
||
if math.isfinite(getattr(row, f"{compiler}_{field}"))]
|
||
y_max = max(available, default=1.0) * 1.35
|
||
fig, axes = plt.subplots(1, 2, sharey=True, figsize=(14, 5.2),
|
||
layout="constrained")
|
||
figure_legend(fig, results, field, absolute=True, raptor_only=raptor_only)
|
||
|
||
for ax, arch in zip(axes, ARCHITECTURES):
|
||
selected = [(row, compiler) for row, compiler in columns if row.arch == arch]
|
||
positions = []
|
||
for index, (row, _) in enumerate(selected):
|
||
if index == 0:
|
||
positions.append(0.0)
|
||
continue
|
||
previous = selected[index - 1][0]
|
||
gap = 0.8 if row.model != previous.model else 0.4 if row.mode != previous.mode else 0.0
|
||
positions.append(positions[-1] + 1.0 + gap)
|
||
ax.set_xlabel("ARCHITECTURE A" if arch == "arch-a" else "ARCHITECTURE B",
|
||
fontsize=11, weight="bold", labelpad=8)
|
||
ax.set_axisbelow(True)
|
||
ax.set_ylim(0, y_max)
|
||
if positions:
|
||
ax.set_xlim(positions[0] - 0.9, positions[-1] + 0.9)
|
||
else:
|
||
ax.set_xlim(-0.9, 0.9)
|
||
label_groups(ax, [row for row, _ in selected], positions)
|
||
if {row.mode for row, _ in selected} == {"latency", "throughput"}:
|
||
centers, labels = [], []
|
||
for (_, mode), group in groupby(enumerate(selected), key=lambda item: (item[1][0].model, item[1][0].mode)):
|
||
group_positions = [positions[position] for position, _ in group]
|
||
centers.append((group_positions[0] + group_positions[-1]) / 2)
|
||
labels.append(mode.capitalize())
|
||
ax.set_xticks(centers, labels, minor=True)
|
||
ax.tick_params(axis="x", which="minor", length=0, pad=4, labelsize=8)
|
||
ax.set_ylabel(ylabel, fontsize=10.5)
|
||
if field == "throughput":
|
||
ax.ticklabel_format(axis="y", style="plain", useOffset=False)
|
||
ax.grid(axis="y", color="#E6EAF0", linewidth=0.8)
|
||
ax.tick_params(axis="y", length=0, pad=6)
|
||
ax.tick_params(axis="x", which="major", length=0,
|
||
pad=22 if {row.mode for row, _ in selected} == {"latency", "throughput"} else 6)
|
||
|
||
for x, (row, compiler) in zip(positions, selected):
|
||
measurement = getattr(row, f"{compiler}_{field}") * scale
|
||
if not math.isfinite(measurement):
|
||
ax.text(x, y_max * 0.02, "N/A", rotation=90, ha="center",
|
||
va="bottom", fontsize=8, color="#85909F")
|
||
continue
|
||
status = getattr(row, f"{compiler}_validation")
|
||
pattern = STAGE_PATTERNS[row.pipeline] if compiler == "raptor" else ""
|
||
ax.bar(x, measurement, width=1.0,
|
||
color=COMPILER_COLORS[compiler],
|
||
hatch=pattern,
|
||
edgecolor=TEXT_COLOR if pattern else "none",
|
||
linewidth=0.7 if pattern else 0.0, zorder=2)
|
||
label = (f"{measurement:,.2f}".rstrip("0").rstrip(".")
|
||
if field == "throughput" else f"{measurement:.3g}")
|
||
value_label(ax, x, measurement + y_max * 0.012, label, status)
|
||
|
||
fit_value_labels(fig)
|
||
save_figure(fig, basename, output_dir)
|
||
|
||
|
||
def draw_pipeline_effect(results: list[Result], output_dir: Path) -> None:
|
||
rows = [row for row in results if row.mode == "throughput" or row.pipeline == 1]
|
||
stages = sorted({row.pipeline for row in rows})
|
||
panels = [(model, color) for model, color in zip(MODELS, COLORS)
|
||
if any(row.model == model for row in rows)]
|
||
lookup = {(row.arch, row.model, row.pipeline): row for row in rows}
|
||
rates = {key: quotient(1000, row.raptor_latency) if row.mode == "latency" else row.raptor_throughput
|
||
for key, row in lookup.items()}
|
||
fig, axes = plt.subplots(len(ARCHITECTURES), len(panels), sharex=True, sharey="col",
|
||
figsize=(3 * len(panels), 7.2), squeeze=False, layout="constrained")
|
||
for column, (model, color) in enumerate(panels):
|
||
for index, arch in enumerate(ARCHITECTURES):
|
||
ax = axes[index, column]
|
||
keys = [(arch, model, stage) for stage in stages]
|
||
values = [rates.get(key, math.nan) for key in keys]
|
||
valid = [key in lookup and lookup[key].raptor_validation == "PASS" for key in keys]
|
||
ax.plot(stages, values, color=color, linewidth=1.8)
|
||
for stage, value, passed in zip(stages, values, valid):
|
||
if math.isfinite(value):
|
||
marker = ("s" if stage == 1 else "o") if passed else "x"
|
||
ax.plot(stage, value, color=color, marker=marker,
|
||
markersize=6, linestyle="none")
|
||
ax.set_xticks(stages)
|
||
ax.set_xlabel(model.upper(), fontsize=10.5, weight="bold", labelpad=8)
|
||
ax.grid(axis="y", color="#E6EAF0", linewidth=0.8)
|
||
ax.tick_params(axis="both", length=0, pad=6, labelbottom=True, labelleft=True)
|
||
ax.ticklabel_format(axis="y", style="plain", useOffset=False)
|
||
if column == 0:
|
||
ax.set_ylabel(f"{arch.replace('arch-', 'ARCHITECTURE ').upper()}\n"
|
||
"Throughput (inferences/s)", fontsize=10.5)
|
||
for ax in axes.flat:
|
||
ax.set_ylim(bottom=0)
|
||
handles = [Line2D([], [], color="#657081", marker=marker, linestyle="none", label=label)
|
||
for marker, label in (("s", "Derived as 1000ms / latency_ms"),
|
||
("o", "Throughput measurement"))]
|
||
if any(row.raptor_validation != "PASS" and math.isfinite(rates[key]) for key, row in lookup.items()):
|
||
handles.append(Line2D([], [], color="#424B58", marker="x", linestyle="none",
|
||
label="Raptor validation not PASS"))
|
||
fig.legend(handles=handles, loc="outside upper center", ncol=len(handles), frameon=False, fontsize=9.5)
|
||
fig.supxlabel("Raptor pipeline stages", fontsize=11)
|
||
save_figure(fig, "raptor_pipeline_throughput", output_dir)
|
||
|
||
|
||
def main() -> None:
|
||
parser = argparse.ArgumentParser(description=__doc__)
|
||
script_dir = Path(__file__).resolve().parent
|
||
parser.add_argument("--input", type=Path, default=script_dir / "results_comparison.csv")
|
||
parser.add_argument("--output-dir", type=Path, default=script_dir / "plots")
|
||
args = parser.parse_args()
|
||
configure_style()
|
||
results = load_results(args.input)
|
||
valid = sum(row.comparison_passed for row in results)
|
||
print(f"Results: {len(results)} configurations, {valid} valid comparisons, "
|
||
f"{len(results)-valid} comparisons without two PASS statuses")
|
||
draw_validation(results, args.output_dir)
|
||
draw_pipeline_effect(results, args.output_dir)
|
||
yolo_only_models = {
|
||
model for model in MODELS if model.startswith("yolo")
|
||
and not any(math.isfinite(getattr(row, f"pimcomp_{field}"))
|
||
for row in results if row.model == model
|
||
for field in ("latency", "power", "energy"))
|
||
}
|
||
comparisons = [row for row in results if row.model not in yolo_only_models]
|
||
yolo_only = [row for row in results if row.model in yolo_only_models]
|
||
for metric, name, label, mode in [
|
||
("latency_speedup", "longest_stage_latency", "Longest stage latency speedup (x)", None),
|
||
("latency_speedup", "latency", "Single-inference latency speedup (x)", "latency"),
|
||
("throughput_speedup", "throughput", "Throughput speedup (x)", "throughput"),
|
||
("power", "power", "Average power advantage (x)", None),
|
||
("energy", "energy", "Energy per inference advantage (x)", None),
|
||
]:
|
||
selected = [row for row in comparisons if mode is None or row.mode == mode]
|
||
draw_metric(selected, metric, label,
|
||
f"{name}_comparison", args.output_dir)
|
||
draw_metric(selected, metric, label,
|
||
f"{name}_diverging", args.output_dir, diverging=True)
|
||
for metric, name, label, scale, mode in [
|
||
("latency", "longest_stage_latency", "Longest stage latency (ms)", 1.0, None),
|
||
("latency", "latency", "Single-inference latency (ms)", 1.0, "latency"),
|
||
("power", "power", "Average power (mW)", 1.0, None),
|
||
("energy", "energy", "Energy per inference (mJ)", 1e-9, None),
|
||
("throughput", "throughput", "Throughput (inferences/s)", 1.0, "throughput"),
|
||
]:
|
||
for suffix, group, raptor_only in (
|
||
("absolute", comparisons, False), ("yolo", yolo_only, True)
|
||
):
|
||
selected = [row for row in group if mode is None or row.mode == mode]
|
||
if not selected:
|
||
continue
|
||
draw_absolute(selected, metric, label,
|
||
f"{name}_{suffix}", args.output_dir, scale,
|
||
raptor_only=raptor_only)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|