even better more structured throughput scheduling
Validate Operations / validate-operations (push) Has been cancelled
Validate Operations / validate-operations (push) Has been cancelled
plot adjustments validation adjustments
This commit is contained in:
@@ -39,13 +39,20 @@ beside this script. An explicit `--out-dir PATH` writes
|
||||
Default model artifacts remain under `validation/networks/pimcomp_models`.
|
||||
Artifacts use
|
||||
`<out-dir>/<model>/artifacts/<arch>/<mode>[/pipelineN]/`, with shared references
|
||||
under `artifacts/common/` (`common-functional/` for YOLO's canonical reference)
|
||||
under `artifacts/common/`
|
||||
and `comparison_report.{md,json}` under each comparison's `pimcomp/`.
|
||||
YOLO and GoogLeNet use `artifacts/common-full-model/` to avoid reusing historical Softmax-free references.
|
||||
Pimcomp runs once per model/architecture/mode; later pipelines reuse its newly
|
||||
written report, including failures without simulation artifacts. Missing or
|
||||
unchanged anchor reports trigger fresh attempts. The runner continues after failures and exits nonzero if a
|
||||
compiler, simulation, or functional comparison fails.
|
||||
|
||||
If fast-mode convergence fails, the reports and CSV retain valid full-duration
|
||||
fallback throughput and output interval values. This is not a converged steady-state result.
|
||||
Per-case JSON records `throughput_measurement: full_duration_fallback` and
|
||||
`fast_throughput_converged: false`; the text report labels the fallback.
|
||||
Zero completed outputs still give `NA` for per-inference metrics.
|
||||
|
||||
Reuse checks model/configuration paths and SHA-256 contents, effective hardware,
|
||||
mode, Pimcomp pipeline/replication, seed, batch, tolerances, and simulation flags.
|
||||
Malformed, incomplete, mismatched, or legacy reports without fingerprints fail
|
||||
@@ -56,6 +63,14 @@ exists. Raptor pipeline depth is deliberately excluded from Pimcomp identity.
|
||||
Tool/build revisions and manually edited reference artifacts are not hashed;
|
||||
rerun the relevant compiler after those change.
|
||||
|
||||
Functional validation uses the same ONNX model and Raptor program as the performance comparison.
|
||||
YOLO uses the full model, including both internal Softmax operations.
|
||||
The functional simulator executes Softmax; Pimsim-nn and the queue estimator omit its timing cost.
|
||||
Reported timing and energy exclude Softmax work. Other instructions and transfers retain their costs.
|
||||
GoogLeNet removes only its two LRN nodes and retains terminal Softmax.
|
||||
VGG8 and both ResNet graphs have no removed operators.
|
||||
The native reference uses the same model as each compiled program.
|
||||
|
||||
## One-model comparator
|
||||
|
||||
`compare_raptor_pimcomp_model.py` requires `--model PATH` and `--out-dir PATH`.
|
||||
@@ -63,6 +78,13 @@ Use `--help` for hardware and simulation options. The suite runner supplies
|
||||
these automatically.
|
||||
The comparator defaults to absolute/relative tolerances `1e-3`/`1e-4`;
|
||||
`validate.py` uses `1e-3`/`1e-5`.
|
||||
Both use the [same output checks](../../../../README.md#performance-and-result-interpretation),
|
||||
including a relative L2 limit for each nonzero reference output.
|
||||
`--rtol=0` disables that additional limit in the comparator.
|
||||
Reports from an older validation policy require regeneration before reuse.
|
||||
An elementwise failure with relative L2 agreement remains `FAIL`.
|
||||
Its report notes possible numerical sensitivity. That note does not establish correctness.
|
||||
Every batch iteration must pass. Reported errors are maxima across iterations for each output.
|
||||
|
||||
## Comparison charts
|
||||
|
||||
@@ -71,19 +93,43 @@ The comparator defaults to absolute/relative tolerances `1e-3`/`1e-4`;
|
||||
```
|
||||
|
||||
Requires Matplotlib from the root `requirements.txt`. Defaults to the supplied
|
||||
`results_comparison.csv` beside the script and writes `plots/png/` and
|
||||
`plots/svg/` and `plots/pdf/` there. Use `--input PATH` for a runner-generated CSV and
|
||||
`results_comparison.csv` beside the script and writes `plots/default/png/`,
|
||||
`plots/default/svg/`, and `plots/default/pdf/` there. Use `--input PATH` for a runner-generated CSV and
|
||||
`--output-dir PATH` to override the destination. The plotter requires the full
|
||||
40-case matrix: five models × Arch-A/B × latency pipeline 1 and throughput
|
||||
pipelines 2/4/8. Duplicate, missing, unexpected, or inconsistent shared Pimcomp
|
||||
configurations are rejected before plots are written.
|
||||
|
||||
Charts use the pastel palette and the Raptor name by default.
|
||||
Use `--no-colors` for grayscale output; `--colors` explicitly enables colors.
|
||||
Use `--anonymize` to replace displayed `Raptor` labels with `Anonimized`;
|
||||
`--no-anonymize` explicitly keeps the Raptor name. The flags are independent.
|
||||
Use `--no-marker-borders` for solid markers without outlines in the compact dot
|
||||
plot and line chart, including their legends. These markers use the darker
|
||||
border colors as solid fills. `--marker-borders` keeps the default outlines.
|
||||
Line colors and bar patterns remain unchanged.
|
||||
Input keys and output basenames remain stable. Use these output directories
|
||||
to keep the grayscale and colored anonymized versions:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/generate_plots.py \
|
||||
--no-colors --anonymize \
|
||||
--output-dir validation/tools/pim/pimcomp/compare/plots/gray_anonimized
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/generate_plots.py \
|
||||
--anonymize \
|
||||
--output-dir validation/tools/pim/pimcomp/compare/plots/anonimized
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/generate_plots.py \
|
||||
--anonymize --no-marker-borders \
|
||||
--output-dir validation/tools/pim/pimcomp/compare/plots/no_border_anonimized
|
||||
```
|
||||
|
||||
| Plot family | Meaning |
|
||||
|---|---|
|
||||
| `validation_status` | Recorded functional status for each compiler and experiment. |
|
||||
| `validation_status_compact` | Compact functional status matrix. |
|
||||
| `speedup_compact_*` | Touching single-inference latency and best-pipeline throughput bars for each network. |
|
||||
| `energy_compact_*`, `power_compact_*` | Energy and power advantages for the same cases selected by the compact speedup plots. |
|
||||
| `performance_energy_compact` | Four horizontal dot panels: latency-mode speedup, throughput-mode speedup, and separate energy panels for both modes. |
|
||||
| `speedup_compact_*` | Latency panel on the left and best-pipeline throughput panel on the right, with touching Arch-A/B bars per network. |
|
||||
| `energy_compact_*`, `power_compact_*` | Energy reduction and power reduction for the same cases selected by the compact speedup plots. |
|
||||
| `latency_*` | Single-inference latency, latency mode only. |
|
||||
| `throughput_*` | Inferences/s, throughput mode only. |
|
||||
| `longest_stage_latency_*` | Combined single-inference latency and throughput output interval; the latter is a stage-latency proxy. |
|
||||
@@ -95,17 +141,47 @@ Suffixes `_absolute` and `_yolo` show measurements (`_yolo` is Raptor-only);
|
||||
advantages in either direction. Ratios above 1 favor Raptor: Pimcomp/Raptor for
|
||||
latency, power, and energy; Raptor/Pimcomp for throughput. Diverging bars use
|
||||
winning factor minus one, with parity at zero.
|
||||
Direct ratio axes start at 0.5, or 0.25 when a result is below 0.5.
|
||||
Panels in each figure share the same minimum.
|
||||
|
||||
The shared `plot_style.py` defines the manuscript palette and display names.
|
||||
Raptor uses sage green; PIMCOMP uses plain sand. Stage patterns,
|
||||
outlines, marker shapes, and status text preserve distinctions in grayscale.
|
||||
Compact plots use blue for the single-inference case and hatched sage
|
||||
green for the throughput case.
|
||||
Compact bar plots use plain blue for Arch-A and hatched sage green for Arch-B.
|
||||
The left panel contains latency-mode measurements. The right panel contains
|
||||
throughput-mode measurements. Each network has touching Arch-A/B bars.
|
||||
All compact figures use the display names `Latency-mode` and `Throughput-mode`.
|
||||
Each architecture/network pair uses the highest recorded throughput across pipelines.
|
||||
Energy and power use those same cases, without selecting a different pipeline for each metric.
|
||||
Validation labels remain visible for the selected cases. Units appear in axis
|
||||
titles and bar values; axis tick labels contain numbers only.
|
||||
|
||||
The `performance_energy_compact` figure includes the eight Arch-A/B comparisons
|
||||
for VGG-8, ResNet-18, ResNet-34, and GoogLeNet. It excludes YOLO11n.
|
||||
All panels share a linear ratio axis from 0.5, or 0.25 when needed, through the largest ratio,
|
||||
with a small right margin. A dashed line marks parity at 1.
|
||||
The shared axis title and ticks appear at the top, below the legend.
|
||||
The `Ratio (x)` title is centered over the axis. Panel titles appear to the left,
|
||||
rotate 90° counterclockwise, and use centered text on at most two lines.
|
||||
Titles start with `Latency-mode` or `Throughput-mode`, followed by the metric.
|
||||
The PIMCOMP-better and Raptor-better indicators flank the shared axis title.
|
||||
The figure has no mean markers.
|
||||
Labels give ratios to two decimal places; marker positions use unrounded ratios.
|
||||
Each panel has separate Arch-A and Arch-B lanes. The legend identifies models
|
||||
through marker shapes and pastel colors. Result labels contain ratios and use
|
||||
vertical text above or below their architecture line, without connectors.
|
||||
Placement prefers above for Arch-A and below for Arch-B.
|
||||
Lane spacing and label placement avoid other labels, markers, and reference lines.
|
||||
Larger dark-red daggers mark invalid PIMCOMP comparisons, including every batch comparison,
|
||||
even when the CSV records PASS. Marker borders retain the model colors;
|
||||
ratio values use normal text colors. The legend dagger matches the result daggers.
|
||||
Both use the same font and size. Labels without daggers reserve the same space,
|
||||
so numeric values align within each lane.
|
||||
These simulated ratios remain visible.
|
||||
The two energy panels show latency-mode energy and throughput-selected energy.
|
||||
The throughput energy uses the selected throughput pipeline, without a separate
|
||||
energy optimization.
|
||||
|
||||
Throughput-stage bars touch within each network in both absolute and advantage
|
||||
plots. Bar-label widths are measured before export; dense figures expand only
|
||||
as needed to contain values and invalidity annotations. Legends use the available
|
||||
@@ -116,6 +192,7 @@ pipeline plot retains its own scale, with y-axis ticks on both architecture rows
|
||||
Only the bottom row shows the shared x-axis ticks. Network names appear above
|
||||
the top row; architecture names appear on the right. That plot
|
||||
trims unused vertical range and is intended to span both manuscript columns.
|
||||
The line chart shows throughput only; validation status does not change its markers or colors.
|
||||
|
||||
Failed validation is labeled `P. INVALID`; other non-PASS statuses keep their
|
||||
names, and missing measurements show `N/A`. Relative bars require both
|
||||
@@ -130,3 +207,15 @@ publication provenance. The checked-in dataset is intentional. See the
|
||||
|
||||
See the [evaluation methodology](../../../../../docs/evaluation/methodology.md#metric-interpretation)
|
||||
for measurement windows, units, numerical validity, and result provenance.
|
||||
|
||||
Report reuse checks model and configuration hashes, compiler and simulator
|
||||
fingerprints, validation policy, actual tolerances, seed, batch size, and
|
||||
simulation options. Artifact signatures check instructions, weights, and memory
|
||||
images. Missing or changed artifacts require regeneration. Redundant Raptor
|
||||
JSON instruction dumps do not affect signatures when native binaries exist.
|
||||
|
||||
`better_compiler` is available only when both compilers pass functional
|
||||
validation. `speedup` retains the diagnostic timing ratio when both timings
|
||||
exist. A timing ratio for a failed implementation does not identify a better
|
||||
correct implementation. Valid-only plot aggregates require both validations
|
||||
to pass.
|
||||
|
||||
@@ -18,7 +18,7 @@ import time
|
||||
import types
|
||||
from collections import Counter
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dataclasses import asdict, dataclass
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Any
|
||||
@@ -45,6 +45,7 @@ from raptor_validation.onnx_utils import (
|
||||
write_inputs_to_memory_bin,
|
||||
)
|
||||
from raptor_validation.raptor import compile_with_raptor
|
||||
from raptor_validation.output_comparison import VALIDATION_POLICY, compare_output_arrays
|
||||
from raptor_validation.pimsim_nn import (
|
||||
export_raptor_pimsim_artifact,
|
||||
parse_pimsim_nn_metrics,
|
||||
@@ -75,6 +76,7 @@ class CompareResult:
|
||||
max_diffs: dict[str, float]
|
||||
status: str = "done"
|
||||
error: str | None = None
|
||||
relative_l2_errors: dict[str, float | None] = field(default_factory=dict)
|
||||
|
||||
|
||||
def load_pimcomp_exporter():
|
||||
@@ -264,20 +266,29 @@ def compare_simulator_outputs(
|
||||
channel_last: bool = False,
|
||||
) -> CompareResult:
|
||||
sim_arrays = parse_pim_simulator_outputs(output_bin, outputs_desc)
|
||||
if len(sim_arrays) != len(outputs_desc):
|
||||
raise ValueError("simulator output count differs from the model output count")
|
||||
max_diffs: dict[str, float] = {}
|
||||
relative_errors: dict[str, float | None] = {}
|
||||
passed = True
|
||||
for sim_array, (idx, name, _, shape) in zip(sim_arrays, outputs_desc):
|
||||
error = None
|
||||
for sim_array, (idx, name, elem_type, shape) in zip(sim_arrays, outputs_desc):
|
||||
if channel_last and len(shape) == 4:
|
||||
n, c, h, w = shape
|
||||
sim_array = sim_array.reshape(n, h, w, c).transpose(0, 3, 1, 2)
|
||||
csv_name = reference_dir / f"output{idx}_{sanitize_output_name(name)}.csv"
|
||||
ref = np.loadtxt(csv_name, delimiter=",", dtype=np.float32).reshape(shape)
|
||||
diff = np.abs(sim_array.astype(np.float64) - ref.astype(np.float64))
|
||||
allowed = threshold + rtol * np.abs(ref.astype(np.float64))
|
||||
max_diffs[name] = float(np.max(diff))
|
||||
if not np.all(diff <= allowed):
|
||||
passed = False
|
||||
return CompareResult(passed=passed, max_diffs=max_diffs)
|
||||
ref = np.loadtxt(csv_name, delimiter=",", dtype=_ONNX_TO_NP[elem_type]).reshape(shape)
|
||||
comparison = compare_output_arrays(sim_array, ref, atol=threshold, rtol=rtol)
|
||||
max_diffs[name] = comparison.max_abs_diff
|
||||
relative_errors[name] = comparison.relative_l2_error
|
||||
passed &= comparison.passed
|
||||
if not comparison.passed and error is None:
|
||||
if (comparison.relative_l2_error is not None
|
||||
and comparison.relative_l2_error <= rtol and not comparison.pointwise_passed):
|
||||
error = f"{name}: elementwise mismatch despite relative L2 agreement; possible numerical sensitivity, not proof of correctness"
|
||||
else:
|
||||
error = f"{name}: output tolerance exceeded"
|
||||
return CompareResult(passed=passed, max_diffs=max_diffs, relative_l2_errors=relative_errors, error=error)
|
||||
|
||||
|
||||
def sanitize_output_name(name: str) -> str:
|
||||
@@ -652,7 +663,9 @@ def run_functional_validation(
|
||||
stage="Run functional simulation",
|
||||
)
|
||||
max_diffs: dict[str, float] = {}
|
||||
relative_errors: dict[str, float | None] = {}
|
||||
failed_iterations = []
|
||||
failure_detail = None
|
||||
for index, reference_dir in enumerate(reference_dirs):
|
||||
result = compare_simulator_outputs(
|
||||
batch_output_dir / f"output_{index:06d}.bin",
|
||||
@@ -664,12 +677,20 @@ def run_functional_validation(
|
||||
)
|
||||
if not result.passed:
|
||||
failed_iterations.append(index)
|
||||
if failure_detail is None:
|
||||
failure_detail = result.error
|
||||
for name, diff in result.max_diffs.items():
|
||||
max_diffs[name] = max(max_diffs.get(name, 0.0), diff)
|
||||
for name, relative in result.relative_l2_errors.items():
|
||||
if relative is not None:
|
||||
relative_errors[name] = max(relative_errors.get(name) or 0.0, relative)
|
||||
else:
|
||||
relative_errors.setdefault(name, None)
|
||||
return CompareResult(
|
||||
passed=not failed_iterations,
|
||||
max_diffs=max_diffs,
|
||||
error=(f"batch iterations failed: {failed_iterations}" if failed_iterations else None),
|
||||
error=(f"batch iterations failed: {failed_iterations}; {failure_detail}" if failed_iterations else None),
|
||||
relative_l2_errors=relative_errors,
|
||||
)
|
||||
|
||||
|
||||
@@ -1126,6 +1147,8 @@ def perf_status(perf: dict[str, Any]) -> str:
|
||||
return "SKIPPED"
|
||||
if perf.get("error"):
|
||||
return "FAILED"
|
||||
if perf.get("throughput_measurement") == "full_duration_fallback":
|
||||
return "DONE (full-duration fallback; unconverged)"
|
||||
return "DONE"
|
||||
|
||||
|
||||
@@ -1163,29 +1186,77 @@ def path_from_report(report: dict[str, Any], name: str) -> Path | None:
|
||||
return Path(value) if value else None
|
||||
|
||||
|
||||
def pimcomp_reuse_context(
|
||||
args: argparse.Namespace,
|
||||
model: Path,
|
||||
functional_model: Path,
|
||||
hardware: dict[str, int],
|
||||
) -> dict[str, Any]:
|
||||
files = {"model": model, "functional_model": functional_model,
|
||||
"config": args.pimcomp_config}
|
||||
digests = {}
|
||||
for path in set(files.values()):
|
||||
with path.open("rb") as stream:
|
||||
digests[path] = hashlib.file_digest(stream, "sha256").hexdigest()
|
||||
return {
|
||||
"version": 1,
|
||||
"files": {name: {"path": str(path), "sha256": digests[path]}
|
||||
for name, path in files.items()},
|
||||
"hardware": hardware,
|
||||
"settings": {name: getattr(args, name) for name in (
|
||||
"pimsim_mode", "pimsim_time_ms", "pimcomp_pipeline",
|
||||
"pimcomp_replication", "pimcomp_model_name", "seed", "batch_size",
|
||||
"threshold", "rtol", "no_fast", "skip_pimsim_nn",
|
||||
)},
|
||||
}
|
||||
def fingerprint_files(files: dict[str, Path]) -> dict[str, Any]:
|
||||
fingerprints = {}
|
||||
for name, path in files.items():
|
||||
digest = None
|
||||
if path.is_file():
|
||||
with path.open("rb") as stream:
|
||||
digest = hashlib.file_digest(stream, "sha256").hexdigest()
|
||||
fingerprints[name] = {"path": str(path), "sha256": digest}
|
||||
return fingerprints
|
||||
|
||||
|
||||
def experiment_context(args, model, functional_model, hardware, side):
|
||||
files = {"model": model, "functional_model": functional_model, "config": args.pimcomp_config,
|
||||
"oracle": args.pimsim_nn_build_dir / "ChipTest",
|
||||
"comparison": Path(__file__),
|
||||
"validation": VALIDATION_DIR / "raptor_validation/output_comparison.py",
|
||||
"functional_simulator": args.pim_simulator_dir / "target/release/pim-simulator"}
|
||||
if side == "raptor":
|
||||
files["compiler"] = args.raptor_path
|
||||
else:
|
||||
files["compiler"] = args.pimcomp_dir / "build/PIMCOMP-NN"
|
||||
files["exporter"] = args.pimcomp_dir / "verification/export_to_pim_simulator.py"
|
||||
files.update({f"frontend/{p.relative_to(args.pimcomp_dir / 'frontend')}": p
|
||||
for p in sorted((args.pimcomp_dir / "frontend").rglob("*.py"))})
|
||||
settings = {name: getattr(args, name) for name in (
|
||||
"pimsim_mode", "pimsim_time_ms", "seed", "batch_size", "threshold", "rtol",
|
||||
"no_fast", "skip_pimsim_nn")}
|
||||
settings.update({name: getattr(args, name) for name in (
|
||||
("raptor_extra_arg",) if side == "raptor" else
|
||||
("pimcomp_pipeline", "pimcomp_replication", "pimcomp_model_name"))})
|
||||
return {"version": 2, "validation_policy": VALIDATION_POLICY,
|
||||
"files": fingerprint_files(files), "hardware": hardware, "settings": settings}
|
||||
|
||||
|
||||
def pimcomp_reuse_context(args, model, functional_model, hardware):
|
||||
return experiment_context(args, model, functional_model, hardware, "pimcomp")
|
||||
|
||||
|
||||
def artifact_signatures(report, side):
|
||||
names = (("raptor_pim", "raptor_pimsim_nn", "reference_runner", "reference_library",
|
||||
"reference_inputs", "batch_inputs") if side == "raptor" else
|
||||
("pimcomp_simulation_info", "pimcomp_exported_pim", "pimcomp_pimsim_nn"))
|
||||
signatures = {}
|
||||
content = {}
|
||||
for name in names:
|
||||
root = path_from_report(report, name)
|
||||
if root is None:
|
||||
continue
|
||||
if not root.exists():
|
||||
signatures[name] = None
|
||||
continue
|
||||
paths = [root] if root.is_file() else sorted(p for p in root.rglob("*") if p.is_file())
|
||||
binary = root.is_dir() and any(root.glob("core_*.pim"))
|
||||
digest = hashlib.sha256()
|
||||
for path in paths:
|
||||
# JSON instruction dumps are redundant when native binaries exist.
|
||||
if binary and path.parent == root and path.match("core_*.json"):
|
||||
continue
|
||||
resolved = path.resolve()
|
||||
if resolved not in content:
|
||||
with path.open("rb") as stream:
|
||||
content[resolved] = hashlib.file_digest(stream, "sha256").hexdigest()
|
||||
digest.update(str(path.relative_to(root) if root.is_dir() else path.name).encode())
|
||||
digest.update(b"\0" + content[resolved].encode() + b"\0")
|
||||
signatures[name] = digest.hexdigest()
|
||||
return signatures
|
||||
|
||||
|
||||
def verify_reused_artifacts(report, side):
|
||||
if report.get("artifact_signatures", {}).get(side) != artifact_signatures(report, side):
|
||||
raise ValueError(f"Reused {side} artifacts differ; regenerate the report")
|
||||
|
||||
|
||||
def load_reused_pimcomp_report(path: Path, context: dict[str, Any] | None) -> dict[str, Any]:
|
||||
@@ -1212,6 +1283,7 @@ def load_reused_pimcomp_report(path: Path, context: dict[str, Any] | None) -> di
|
||||
restore_side_records(report, "pimcomp", [], [])
|
||||
for name in ("pimcomp_simulation_info", "pimcomp_exported_pim", "pimcomp_pimsim_nn", "pimsim_config"):
|
||||
path_from_report(report, name)
|
||||
verify_reused_artifacts(report, "pimcomp")
|
||||
return report
|
||||
except (OSError, ValueError, TypeError, AttributeError, KeyError) as exc:
|
||||
raise ValueError(f"Cannot reuse Pimcomp report {path}: {exc}. Rerun Pimcomp to regenerate it.") from exc
|
||||
@@ -1334,14 +1406,19 @@ def write_report(
|
||||
if pimcomp_validation.error:
|
||||
lines.append(f"- Pimcomp validation note: `{pimcomp_validation.error.splitlines()[0]}`")
|
||||
|
||||
lines.extend(["", "### Max output differences", ""])
|
||||
lines.extend(["", "### Output errors", ""])
|
||||
diff_names = sorted(set(raptor_validation.max_diffs) | set(pimcomp_validation.max_diffs))
|
||||
if diff_names:
|
||||
lines.extend(["| Output | Raptor max diff | Pimcomp max diff |", "|---|---:|---:|"])
|
||||
lines.extend(["| Output | Raptor max diff | Pimcomp max diff | Raptor relative L2 | Pimcomp relative L2 |",
|
||||
"|---|---:|---:|---:|---:|"])
|
||||
for name in diff_names:
|
||||
relative = [result.relative_l2_errors.get(name)
|
||||
for result in (raptor_validation, pimcomp_validation)]
|
||||
relative_text = [f"{value:.6e}" if value is not None else "N/A" for value in relative]
|
||||
lines.append(
|
||||
f"| `{name}` | {raptor_validation.max_diffs.get(name, float('nan')):.6e} | "
|
||||
f"{pimcomp_validation.max_diffs.get(name, float('nan')):.6e} |"
|
||||
f"{pimcomp_validation.max_diffs.get(name, float('nan')):.6e} | "
|
||||
f"{relative_text[0]} | {relative_text[1]} |"
|
||||
)
|
||||
else:
|
||||
lines.append("No output differences are available because validation did not run or failed before comparison.")
|
||||
@@ -1358,24 +1435,24 @@ def write_report(
|
||||
if pimsim_mode == "throughput":
|
||||
lines.extend(
|
||||
[
|
||||
"| Compiler | Status | Avg latency | Throughput | Avg power | Avg energy | Output count |",
|
||||
"| Compiler | Status | Output interval (ms) | Throughput | Average power (mW) | Energy per inference (pJ/inference) | Output count |",
|
||||
"|---|---|---:|---:|---:|---:|---:|",
|
||||
f"| Raptor | {perf_status(raptor_perf)} | {perf_value(raptor_perf, 'average_latency_ms', 'ms')} | {perf_value(raptor_perf, 'throughput', 'samples/s')} | "
|
||||
f"{perf_value(raptor_perf, 'average_power_mw', 'mW')} | {perf_value(raptor_perf, 'average_energy_pj', 'pJ/it')} | {perf_value(raptor_perf, 'output_count')} |",
|
||||
f"{perf_value(raptor_perf, 'average_power_mw', 'mW')} | {perf_value(raptor_perf, 'average_energy_pj', 'pJ/inference')} | {perf_value(raptor_perf, 'output_count')} |",
|
||||
f"| Pimcomp | {perf_status(pimcomp_perf)} | {perf_value(pimcomp_perf, 'average_latency_ms', 'ms')} | {perf_value(pimcomp_perf, 'throughput', 'samples/s')} | "
|
||||
f"{perf_value(pimcomp_perf, 'average_power_mw', 'mW')} | {perf_value(pimcomp_perf, 'average_energy_pj', 'pJ/it')} | {perf_value(pimcomp_perf, 'output_count')} |",
|
||||
f"{perf_value(pimcomp_perf, 'average_power_mw', 'mW')} | {perf_value(pimcomp_perf, 'average_energy_pj', 'pJ/inference')} | {perf_value(pimcomp_perf, 'output_count')} |",
|
||||
"",
|
||||
]
|
||||
)
|
||||
else:
|
||||
lines.extend(
|
||||
[
|
||||
"| Compiler | Status | Latency | Avg power | Avg energy |",
|
||||
"| Compiler | Status | Latency (ms) | Average power (mW) | Energy per inference (pJ/inference) |",
|
||||
"|---|---|---:|---:|---:|",
|
||||
f"| Raptor | {perf_status(raptor_perf)} | {perf_value(raptor_perf, 'latency_ms', 'ms')} | "
|
||||
f"{perf_value(raptor_perf, 'average_power_mw', 'mW')} | {perf_value(raptor_perf, 'average_energy_pj', 'pJ')} |",
|
||||
f"{perf_value(raptor_perf, 'average_power_mw', 'mW')} | {perf_value(raptor_perf, 'average_energy_pj', 'pJ/inference')} |",
|
||||
f"| Pimcomp | {perf_status(pimcomp_perf)} | {perf_value(pimcomp_perf, 'latency_ms', 'ms')} | "
|
||||
f"{perf_value(pimcomp_perf, 'average_power_mw', 'mW')} | {perf_value(pimcomp_perf, 'average_energy_pj', 'pJ')} |",
|
||||
f"{perf_value(pimcomp_perf, 'average_power_mw', 'mW')} | {perf_value(pimcomp_perf, 'average_energy_pj', 'pJ/inference')} |",
|
||||
"",
|
||||
]
|
||||
)
|
||||
@@ -1471,8 +1548,10 @@ def main():
|
||||
parser.add_argument("--pim-simulator-dir", default=REPO / "backend-simulators/pim/pim-simulator", type=Path)
|
||||
parser.add_argument("--pimsim-nn-build-dir", default=REPO / "backend-simulators/pim/pimsim-nn/build", type=Path)
|
||||
parser.add_argument("--seed", type=int, default=0)
|
||||
parser.add_argument("--threshold", type=float, default=1e-3)
|
||||
parser.add_argument("--rtol", type=float, default=1e-4)
|
||||
parser.add_argument("--threshold", type=float, default=1e-3,
|
||||
help="Absolute tolerance for per-element output comparison.")
|
||||
parser.add_argument("--rtol", type=float, default=1e-4,
|
||||
help="Relative tolerance for per-element and output L2 comparison; 0 disables the L2 check.")
|
||||
parser.add_argument(
|
||||
"--timeout-seconds",
|
||||
type=float,
|
||||
@@ -1671,6 +1750,14 @@ def main():
|
||||
reuse_report_path = args.reuse_raptor_report.resolve()
|
||||
with open(reuse_report_path, "r", encoding="utf-8") as f:
|
||||
reused = json.load(f)
|
||||
if reused.get("validation_policy") != VALIDATION_POLICY:
|
||||
raise ValueError("Reused Raptor validation policy differs; regenerate the Raptor report")
|
||||
if reused.get("validation_tolerances") != {"atol": args.threshold, "rtol": args.rtol}:
|
||||
raise ValueError("Reused Raptor validation tolerances differ; regenerate the Raptor report")
|
||||
expected = experiment_context(args, model_path, functional_model_path, hardware, "raptor")
|
||||
if reused.get("raptor_reuse_context") != expected:
|
||||
raise ValueError("Reused Raptor model, compiler, simulation settings, or inputs differ; regenerate the report")
|
||||
verify_reused_artifacts(reused, "raptor")
|
||||
reused_hardware = reused["hardware"]
|
||||
if reused_hardware != hardware:
|
||||
raise ValueError(f"Reused Raptor hardware differs: {reused_hardware} != {hardware}")
|
||||
@@ -1848,13 +1935,13 @@ def main():
|
||||
)
|
||||
raptor_validation = validation if validation is not None else failed_validation("Raptor validation failed")
|
||||
elif not reference_dirs:
|
||||
raptor_validation = skipped_validation("Reference outputs are not available")
|
||||
raptor_validation = failed_validation("Reference outputs are not available")
|
||||
elif not outputs_desc:
|
||||
raptor_validation = skipped_validation("Output descriptors are not available")
|
||||
raptor_validation = failed_validation("Output descriptors are not available")
|
||||
else:
|
||||
raptor_validation = skipped_validation("Raptor input materialization failed")
|
||||
raptor_validation = failed_validation("Raptor input materialization failed")
|
||||
elif not reuse_raptor:
|
||||
raptor_validation = skipped_validation("Raptor Pim compilation did not produce a Pim directory")
|
||||
raptor_validation = failed_validation("Raptor Pim compilation did not produce a Pim directory")
|
||||
|
||||
pimcomp_model_path = model_path
|
||||
|
||||
@@ -2058,6 +2145,8 @@ def main():
|
||||
)
|
||||
|
||||
json_report = {
|
||||
"validation_policy": VALIDATION_POLICY,
|
||||
"validation_tolerances": {"atol": args.threshold, "rtol": args.rtol},
|
||||
"model": str(model_path),
|
||||
"functional_model": str(functional_model_path),
|
||||
"hardware": hardware,
|
||||
@@ -2066,7 +2155,9 @@ def main():
|
||||
"pimcomp_pipeline": args.pimcomp_pipeline,
|
||||
"pimcomp_replication": args.pimcomp_replication,
|
||||
"pimcomp_model_source": "not_run" if args.raptor_only else "original_onnx",
|
||||
"pimcomp_reuse_context": reuse_context,
|
||||
"pimcomp_reuse_context": (reuse_context if reuse_pimcomp else
|
||||
None if args.raptor_only else pimcomp_reuse_context(args, model_path, functional_model_path, hardware)),
|
||||
"raptor_reuse_context": experiment_context(args, model_path, functional_model_path, hardware, "raptor"),
|
||||
"pimcomp_config": str(args.pimcomp_config),
|
||||
"raptor_extra_args": args.raptor_extra_arg,
|
||||
"reused_raptor_report": optional_path(args.reuse_raptor_report.resolve()) if reuse_raptor else None,
|
||||
@@ -2087,6 +2178,8 @@ def main():
|
||||
"batch_inputs": optional_path(out_dir / "inputs.csv"),
|
||||
"batch_outputs": optional_path(out_dir / "simulation/out_iterations"),
|
||||
"reference_runner": optional_path(runner_path),
|
||||
"reference_library": optional_path(common_dir / "runner" / f"{functional_model_path.stem}.so")
|
||||
if (common_dir / "runner" / f"{functional_model_path.stem}.so").is_file() else None,
|
||||
"raptor_pim": optional_path(raptor_pim_dir),
|
||||
"raptor_functional_pim": optional_path(raptor_functional_pim_dir),
|
||||
"raptor_pimsim_nn": optional_path(raptor_pimsim_dir),
|
||||
@@ -2097,6 +2190,8 @@ def main():
|
||||
"report_markdown": str(report_path),
|
||||
},
|
||||
}
|
||||
json_report["artifact_signatures"] = {
|
||||
side: artifact_signatures(json_report, side) for side in ("raptor", "pimcomp")}
|
||||
json_path = out_dir / "pimcomp/comparison_report.json"
|
||||
with open(json_path, "w", encoding="utf-8") as f:
|
||||
json.dump(json_report, f, indent=2)
|
||||
|
||||
@@ -11,6 +11,7 @@ beside it, with outputs grouped in png/, svg/, and pdf/ under the output directo
|
||||
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.
|
||||
A compact dot-plot summary combines latency, throughput, and both energy cases.
|
||||
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.
|
||||
@@ -36,11 +37,13 @@ matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import Patch, Rectangle
|
||||
from matplotlib.lines import Line2D
|
||||
from matplotlib.text import Text
|
||||
from matplotlib.ticker import FuncFormatter, MultipleLocator
|
||||
from matplotlib.transforms import Bbox
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
from plot_style import (ARCH_NAMES, BAND_COLOR, COMPILER_NAMES, COMPILER_STYLES,
|
||||
ERROR_COLOR, LINE_COLOR, MATRIX_TEXT_COLOR, METRIC_STYLES,
|
||||
from plot_style import (ARCH_NAMES, ARCH_STYLES, BAND_COLOR, COMPACT_MODE_NAMES, COMPILER_NAMES, COMPILER_STYLES,
|
||||
DAGGER_COLOR, ERROR_COLOR, LINE_COLOR, MATRIX_TEXT_COLOR,
|
||||
MODE_NAMES, MODEL_NAMES, NETWORK_STYLES,
|
||||
PALETTE, TEXT_COLOR, add_legend, configure_style, fit_value_labels,
|
||||
fit_bar_widths, fit_x_tick_labels, save_figure, value_label)
|
||||
@@ -276,7 +279,7 @@ def draw_validation(results: list[Result], output_dir: Path, compact: bool = Fal
|
||||
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" and not compact
|
||||
headers = [f"{COMPILER_NAMES['raptor']}\n{stage} stages" if mode == "throughput" and compiler == "raptor" and not compact
|
||||
else "PIMCOMP\nbatch" if mode == "throughput" and compiler == "pimcomp" and not compact
|
||||
else COMPILER_NAMES[compiler]
|
||||
for mode, stage, compiler in configurations]
|
||||
@@ -288,7 +291,8 @@ def draw_validation(results: list[Result], output_dir: Path, compact: bool = Fal
|
||||
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("Latency" if mode == "latency" else MODE_NAMES[mode])
|
||||
modes.append(COMPACT_MODE_NAMES[mode] if compact else
|
||||
"Latency" if mode == "latency" else MODE_NAMES[mode])
|
||||
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,
|
||||
@@ -320,6 +324,140 @@ def select_compact_speedups(results: list[Result]) -> list[Result]:
|
||||
return selected
|
||||
|
||||
|
||||
def draw_compact_summary(results: list[Result], output_dir: Path, *, marker_borders: bool = True) -> None:
|
||||
selected = select_compact_speedups([row for row in results if row.model != "yolo11n"])
|
||||
lookup = {(row.model, row.arch, row.mode): row for row in selected}
|
||||
keys = [(model, arch) for model in MODELS if model != "yolo11n" for arch in ARCHITECTURES]
|
||||
panels = ((f"{COMPACT_MODE_NAMES['latency']}\nspeedup", "latency", "latency_speedup"),
|
||||
(f"{COMPACT_MODE_NAMES['throughput']}\nspeedup", "throughput", "throughput_speedup"),
|
||||
(f"{COMPACT_MODE_NAMES['latency']}\nenergy reduction", "latency", "energy"),
|
||||
(f"{COMPACT_MODE_NAMES['throughput']}\nenergy reduction", "throughput", "energy"))
|
||||
measurements = []
|
||||
for _, mode, field in panels:
|
||||
points = []
|
||||
for model, arch in keys:
|
||||
row = lookup[(model, arch, mode)]
|
||||
ratio = getattr(row, field)
|
||||
if not math.isfinite(ratio) or ratio <= 0:
|
||||
raise ValueError(f"Compact summary requires a positive {field} ratio for {arch}/{model}")
|
||||
pimcomp_invalid = row.pimcomp_schedule == "batch" or row.pimcomp_validation == "FAIL"
|
||||
status = ("P. INVALID" if pimcomp_invalid else
|
||||
f"{COMPILER_NAMES['raptor'][0]}. INVALID" if row.raptor_validation == "FAIL" else
|
||||
"NOT VALIDATED" if not row.comparison_passed else "")
|
||||
points.append((row, ratio, status))
|
||||
measurements.append(points)
|
||||
ratios = [ratio for points in measurements for _, ratio, _ in points]
|
||||
padding = max(max(ratios) - min(ratios), 1) * 0.04
|
||||
limits = (0.25 if min(ratios) < 0.5 else 0.5, max(1, *ratios) + padding)
|
||||
fig, axes = plt.subplots(4, 1, sharex=True, figsize=(6.8, 7.6), layout="constrained")
|
||||
markers = dict(zip((model for model in MODELS if model != "yolo11n"), ("o", "s", "^", "D")))
|
||||
lanes = {arch: index * 2 for index, arch in enumerate(ARCHITECTURES)}
|
||||
daggers = {}
|
||||
for ax, (title, _, _), points in zip(axes, panels, measurements):
|
||||
ax.set_xlim(*limits)
|
||||
ax.set_ylim(4, -2)
|
||||
ax.set_ylabel(title, rotation=90, ha="center", va="bottom", multialignment="center",
|
||||
fontsize=8.5, labelpad=10)
|
||||
ax.set_yticks(list(lanes.values()), [ARCH_NAMES[arch] for arch in lanes], fontsize=8)
|
||||
ax.set_xticks([limits[0], *range(1, math.floor(limits[1]) + 1)])
|
||||
ax.xaxis.set_major_formatter(FuncFormatter(lambda value, _: f"{value:g}"))
|
||||
ax.tick_params(axis="both", length=0, pad=4, labelsize=8.5)
|
||||
ax.tick_params(axis="x", bottom=False, labelbottom=False,
|
||||
top=ax is axes[0], labeltop=ax is axes[0])
|
||||
ax.grid(axis="x", linewidth=0.6)
|
||||
ax.axvline(1, color=TEXT_COLOR, linewidth=0.9, linestyle="--", zorder=1)
|
||||
for lane in lanes.values():
|
||||
ax.axhline(lane, color=LINE_COLOR, linewidth=0.6)
|
||||
ax.spines["left"].set_visible(False)
|
||||
ax.spines["bottom"].set_visible(False)
|
||||
ax.spines["top"].set_visible(ax is axes[0])
|
||||
for row, ratio, status in points:
|
||||
fill, border = NETWORK_STYLES[row.model]
|
||||
lane = lanes[row.arch]
|
||||
ax.plot(ratio, lane, marker=markers[row.model], markersize=6.5, linestyle="none",
|
||||
markerfacecolor=fill if marker_borders else border, markeredgecolor=border,
|
||||
markeredgewidth=1.1 if marker_borders else 0)
|
||||
suffix = f" [{status}]" if status and status != "P. INVALID" else ""
|
||||
label = f"{ratio:.2f}×{suffix}"
|
||||
annotation = ax.annotate(label, (ratio, lane), xytext=(0, -5), textcoords="offset points",
|
||||
rotation=90, rotation_mode="anchor", ha="right", va="center",
|
||||
fontsize=7.5, color=TEXT_COLOR, math_fontfamily="cm")
|
||||
annotation.set_in_layout(False)
|
||||
dagger = ax.annotate(r"$\boldsymbol{\dagger}$", (0.5, 1), xycoords=annotation,
|
||||
xytext=(0, 1), textcoords="offset points", rotation=90,
|
||||
rotation_mode="anchor", ha="left", va="center", fontsize=9,
|
||||
color=DAGGER_COLOR, math_fontfamily="cm",
|
||||
alpha=1 if status == "P. INVALID" else 0)
|
||||
dagger.set_in_layout(False)
|
||||
daggers[annotation] = dagger
|
||||
handles = [Line2D([], [], marker=marker, color=NETWORK_STYLES[model][1],
|
||||
markerfacecolor=NETWORK_STYLES[model][0 if marker_borders else 1], markersize=6.5,
|
||||
markeredgewidth=1 if marker_borders else 0,
|
||||
linestyle="none", label=MODEL_NAMES[model])
|
||||
for model, marker in markers.items()]
|
||||
handles.append(Text(0, 0, r"$\boldsymbol{\dagger}$", color=DAGGER_COLOR, fontsize=9,
|
||||
math_fontfamily="cm", label="PIMCOMP invalid"))
|
||||
add_legend(fig, handles)
|
||||
axes[0].xaxis.set_label_position("top")
|
||||
axes[0].set_xlabel("Ratio (x)", fontsize=9, labelpad=7, va="bottom")
|
||||
axes[0].xaxis.set_label_coords(0.5, 1.18)
|
||||
axes[0].text(0, 1.18, "← PIMCOMP better", transform=axes[0].transAxes, ha="left", va="bottom", fontsize=8)
|
||||
axes[0].text(1, 1.18, f"{COMPILER_NAMES['raptor']} better →", transform=axes[0].transAxes,
|
||||
ha="right", va="bottom", fontsize=8)
|
||||
fig.canvas.draw()
|
||||
renderer = fig.canvas.get_renderer()
|
||||
|
||||
def label_bounds(text):
|
||||
return Bbox.union([text.get_window_extent(renderer), daggers[text].get_window_extent(renderer)])
|
||||
|
||||
def position_label(text, dx, dy):
|
||||
text.set_ha("left" if dy > 0 else "right")
|
||||
extra = (daggers[text].get_window_extent(renderer).height + renderer.points_to_pixels(1)
|
||||
if dy < 0 else 0)
|
||||
text.set_position((dx, dy - extra * 72 / fig.dpi))
|
||||
|
||||
labels_by_axis = [[text for text in ax.texts if getattr(text, "xycoords", None) == "data"] for ax in axes]
|
||||
label_height = max(label_bounds(text).height for labels in labels_by_axis for text in labels)
|
||||
lane_gap = min(abs(ax.transData.transform((0, 0))[1] - ax.transData.transform((0, 2))[1])
|
||||
for ax in axes)
|
||||
height_scale = (label_height + renderer.points_to_pixels(5)) / lane_gap
|
||||
if height_scale > 1:
|
||||
fig.set_size_inches(fig.get_figwidth(), fig.get_figheight() * height_scale)
|
||||
fig.canvas.draw()
|
||||
renderer = fig.canvas.get_renderer()
|
||||
for ax, labels in zip(axes, labels_by_axis):
|
||||
placed = []
|
||||
for line in ax.lines:
|
||||
placed.append(line.get_window_extent(renderer).padded(1))
|
||||
bounds = ax.get_window_extent(renderer)
|
||||
choices = []
|
||||
for text in labels:
|
||||
preferred_dy = 5 if text.xy[1] == lanes[ARCHITECTURES[0]] else -5
|
||||
candidates = []
|
||||
for dx in (0, -3, 3, -6, 6, -8, 8):
|
||||
for dy in (-5, 5):
|
||||
position_label(text, dx, dy)
|
||||
box = label_bounds(text)
|
||||
overlap = any(box.overlaps(other) for other in placed)
|
||||
outside = not (bounds.x0 <= box.x0 <= box.x1 <= bounds.x1 and
|
||||
bounds.y0 <= box.y0 <= box.y1 <= bounds.y1)
|
||||
if not outside and not overlap:
|
||||
candidates.append(((dy != preferred_dy, abs(dx) + abs(dy)), dx, dy, box))
|
||||
choices.append((text, candidates))
|
||||
layouts = [((0, 0), [])]
|
||||
for text, candidates in sorted(choices, key=lambda choice: len(choice[1])):
|
||||
layouts = sorted(
|
||||
(((cost[0] + distance[0], cost[1] + distance[1]), assignments + [(text, dx, dy, box)])
|
||||
for cost, assignments in layouts for distance, dx, dy, box in candidates
|
||||
if not any(box.overlaps(other[3]) for other in assignments)),
|
||||
key=lambda layout: layout[0])[:32]
|
||||
if not layouts:
|
||||
raise ValueError("Dot labels do not fit without overlap; increase lane spacing or figure width")
|
||||
for text, dx, dy, _ in layouts[0][1]:
|
||||
position_label(text, dx, dy)
|
||||
save_figure(fig, "performance_energy_compact", output_dir)
|
||||
|
||||
|
||||
def draw_metric(results: list[Result], field: str, ylabel: str,
|
||||
basename: str, output_dir: Path, diverging: bool = False,
|
||||
compact: bool = False) -> None:
|
||||
@@ -327,48 +465,52 @@ def draw_metric(results: list[Result], field: str, ylabel: str,
|
||||
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
|
||||
lower = (min(0, min(available, default=0)) if diverging else
|
||||
0.25 if min(available, default=1) < 0.5 else 0.5)
|
||||
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_min = math.floor((lower - padding) * 2) / 2 if diverging else lower
|
||||
y_interval = 1.0 if y_max - y_min > 4.5 else 0.5
|
||||
|
||||
fig, axes = plt.subplots(1, 2, sharey=True, figsize=(8.5 if compact else 12, 4.4),
|
||||
layout="constrained")
|
||||
if compact:
|
||||
single_metric = "latency" if field == "speedup" else field
|
||||
throughput_suffix = "" if field == "speedup" else f" {field}"
|
||||
handles = [Patch(facecolor=METRIC_STYLES["latency"][0],
|
||||
edgecolor=METRIC_STYLES["latency"][1],
|
||||
label=f"Single-inference {single_metric}"),
|
||||
Patch(facecolor=METRIC_STYLES["throughput"][0],
|
||||
edgecolor=METRIC_STYLES["throughput"][1], hatch="//",
|
||||
label=f"Best-case throughput{throughput_suffix}")]
|
||||
metric_suffix = "" if field == "speedup" else f" {field}"
|
||||
panel_labels = {mode: label + metric_suffix for mode, label in COMPACT_MODE_NAMES.items()}
|
||||
handles = [Patch(facecolor=fill, edgecolor=border, hatch=pattern, label=ARCH_NAMES[arch])
|
||||
for arch, (fill, border, pattern) in ARCH_STYLES.items()]
|
||||
|
||||
for ax, arch in zip(axes, ARCHITECTURES):
|
||||
selected = [row for row in results if row.arch == arch]
|
||||
panels = ("latency", "throughput") if compact else ARCHITECTURES
|
||||
for panel_index, (ax, panel) in enumerate(zip(axes, panels)):
|
||||
selected = [row for row in results if getattr(row, "mode" if compact else "arch") == panel]
|
||||
if compact:
|
||||
selected.sort(key=lambda row: (MODELS.index(row.model), ARCHITECTURES.index(row.arch)))
|
||||
positions = [index + index // 2 for index in range(len(selected))] if compact else grouped_positions(selected)
|
||||
ax.set_xlabel(ARCH_NAMES[arch],
|
||||
ax.set_xlabel(panel_labels[panel] if compact else ARCH_NAMES[panel],
|
||||
fontsize=11, weight="bold", labelpad=8)
|
||||
ax.set_axisbelow(True)
|
||||
ax.set_ylim(y_min, y_max)
|
||||
ax.set_xlim(-0.9, positions[-1] + 0.9 if positions else 0.9)
|
||||
label_groups(ax, selected, positions)
|
||||
ax.tick_params(axis="y", labelleft=arch == ARCHITECTURES[0], pad=5, length=0)
|
||||
ax.tick_params(axis="y", labelleft=panel_index == 0, 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}"))
|
||||
else:
|
||||
ax.set_yticks([y_min], minor=True)
|
||||
ax.yaxis.set_minor_formatter(FuncFormatter(lambda value, _: f"{value:g}"))
|
||||
ax.tick_params(axis="y", which="minor", labelleft=panel_index == 0, pad=5, length=0)
|
||||
ax.grid(axis="y")
|
||||
ax.axhline(0 if diverging else 1, color=TEXT_COLOR, linewidth=1.0,
|
||||
linestyle="--", zorder=3)
|
||||
if arch == ARCHITECTURES[0]:
|
||||
if panel_index == 0:
|
||||
ax.set_ylabel(ylabel, fontsize=10.5, labelpad=6)
|
||||
|
||||
for position, row in zip(positions, 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",
|
||||
ax.text(position, (0 if diverging else y_min) + (y_max - y_min) * 0.02, "N/A", ha="center", va="bottom",
|
||||
fontsize=9.5, color=TEXT_COLOR, fontstyle="italic")
|
||||
continue
|
||||
compiler = advantage_compiler(ratio)
|
||||
@@ -377,8 +519,7 @@ def draw_metric(results: list[Result], field: str, ylabel: str,
|
||||
pattern = STAGE_PATTERNS[row.pipeline]
|
||||
color, border = COMPILER_STYLES[compiler] if compiler else ("white", TEXT_COLOR)
|
||||
if compact:
|
||||
pattern = "//" if row.mode == "throughput" else ""
|
||||
color, border = METRIC_STYLES[row.mode]
|
||||
color, border, pattern = ARCH_STYLES[row.arch]
|
||||
measurement = diverging_ratio(ratio) if diverging else ratio
|
||||
ax.bar(position, measurement, width=1.0,
|
||||
color=color,
|
||||
@@ -395,7 +536,7 @@ def draw_metric(results: list[Result], field: str, ylabel: str,
|
||||
else:
|
||||
figure_legend(fig, results, field)
|
||||
fit_x_tick_labels(fig)
|
||||
fit_value_labels(fig)
|
||||
fit_value_labels(fig, lower_limit=None if diverging else y_min)
|
||||
save_figure(fig, basename, output_dir)
|
||||
|
||||
|
||||
@@ -463,7 +604,7 @@ def draw_absolute(results: list[Result], field: str, ylabel: str,
|
||||
save_figure(fig, basename, output_dir)
|
||||
|
||||
|
||||
def draw_pipeline_effect(results: list[Result], output_dir: Path) -> None:
|
||||
def draw_pipeline_effect(results: list[Result], output_dir: Path, *, marker_borders: bool = True) -> 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 for model in MODELS if any(row.model == model for row in rows)]
|
||||
@@ -479,15 +620,12 @@ def draw_pipeline_effect(results: list[Result], output_dir: Path) -> None:
|
||||
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=border, linewidth=1.8)
|
||||
for stage, value, passed in zip(stages, values, valid):
|
||||
for stage, value in zip(stages, values):
|
||||
if math.isfinite(value):
|
||||
marker = ("s" if stage == 1 else "o") if passed else "x"
|
||||
status = lookup[(arch, model, stage)].raptor_validation
|
||||
ax.plot(stage, value, color=ERROR_COLOR if status == "FAIL" else border,
|
||||
markerfacecolor=fill, marker=marker,
|
||||
markersize=6, linestyle="none")
|
||||
ax.plot(stage, value, color=border,
|
||||
markerfacecolor=fill if marker_borders else border, marker="s" if stage == 1 else "o",
|
||||
markeredgewidth=1 if marker_borders else 0, markersize=7, linestyle="none")
|
||||
ax.set_xticks(stages)
|
||||
if index == 0:
|
||||
ax.set_title(MODEL_NAMES[model], fontsize=10.5, weight="normal", pad=8)
|
||||
@@ -500,15 +638,13 @@ def draw_pipeline_effect(results: list[Result], output_dir: Path) -> None:
|
||||
ax.set_ylabel(ARCH_NAMES[arch], fontsize=10.5, weight="bold", rotation=270, labelpad=24)
|
||||
for ax in axes.flat:
|
||||
ax.margins(y=0.08)
|
||||
handles = [Line2D([], [], color=TEXT_COLOR, markerfacecolor="white",
|
||||
marker=marker, linestyle="none", label=label)
|
||||
handles = [Line2D([], [], color=TEXT_COLOR, markerfacecolor="white" if marker_borders else TEXT_COLOR,
|
||||
markeredgewidth=1 if marker_borders else 0,
|
||||
marker=marker, markersize=7, linestyle="none", label=label)
|
||||
for marker, label in (("s", "Derived as 1 / latency"),
|
||||
("o", "Throughput measurement"))]
|
||||
if any(row.raptor_validation != "PASS" and math.isfinite(rates[key]) for key, row in lookup.items()):
|
||||
handles.append(Line2D([], [], color=TEXT_COLOR, marker="x", linestyle="none",
|
||||
label="Raptor validation not PASS"))
|
||||
add_legend(fig, handles)
|
||||
fig.supxlabel("Raptor pipeline stages", fontsize=11)
|
||||
fig.supxlabel(f"{COMPILER_NAMES['raptor']} pipeline stages", fontsize=11)
|
||||
fit_x_tick_labels(fig)
|
||||
save_figure(fig, "raptor_pipeline_throughput", output_dir)
|
||||
|
||||
@@ -517,16 +653,22 @@ 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")
|
||||
parser.add_argument("--output-dir", type=Path, default=script_dir / "plots/default")
|
||||
parser.add_argument("--colors", action=argparse.BooleanOptionalAction, default=True,
|
||||
help="Use the pastel palette (default); --no-colors exports grayscale charts")
|
||||
parser.add_argument("--anonymize", action=argparse.BooleanOptionalAction, default=False,
|
||||
help="Replace the displayed Raptor name with Anonimized")
|
||||
parser.add_argument("--marker-borders", action=argparse.BooleanOptionalAction, default=True,
|
||||
help="Outline markers (default); --no-marker-borders uses solid markers in dot and line charts")
|
||||
args = parser.parse_args()
|
||||
configure_style()
|
||||
configure_style(colors=args.colors, anonymize=args.anonymize)
|
||||
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_validation(results, args.output_dir, compact=True)
|
||||
draw_pipeline_effect(results, args.output_dir)
|
||||
draw_pipeline_effect(results, args.output_dir, marker_borders=args.marker_borders)
|
||||
yolo_only_models = {
|
||||
model for model in MODELS if model.startswith("yolo")
|
||||
and not any(math.isfinite(getattr(row, f"pimcomp_{field}"))
|
||||
@@ -536,9 +678,10 @@ def main() -> None:
|
||||
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]
|
||||
compact_speedups = select_compact_speedups(comparisons)
|
||||
draw_compact_summary(comparisons, args.output_dir, marker_borders=args.marker_borders)
|
||||
for metric, label in (("speedup", "Speedup (x)"),
|
||||
("energy", "Energy-per-inference advantage (x)"),
|
||||
("power", "Average power advantage (x)")):
|
||||
("energy", "Energy reduction (x)"),
|
||||
("power", "Average power reduction (x)")):
|
||||
for suffix, diverging in (("comparison", False), ("diverging", True)):
|
||||
draw_metric(compact_speedups, metric, label,
|
||||
f"{metric}_compact_{suffix}", args.output_dir, diverging=diverging, compact=True)
|
||||
@@ -546,8 +689,8 @@ def main() -> None:
|
||||
("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),
|
||||
("power", "power", "Average power reduction (x)", None),
|
||||
("energy", "energy", "Energy reduction (x)", None),
|
||||
]:
|
||||
selected = [row for row in comparisons if mode is None or row.mode == mode]
|
||||
draw_metric(selected, metric, label,
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
arch,model,mode,raptor_pipeline,pimcomp_pipeline,raptor_functional_validation,pimcomp_functional_validation,raptor_latency_ms,pimcomp_latency_ms,raptor_throughput_samples_s,pimcomp_throughput_samples_s,raptor_power_mw,pimcomp_power_mw,raptor_energy_pj,pimcomp_energy_pj,better_compiler,speedup
|
||||
arch-a,vgg8,latency,1,element,PASS,PASS,1.52,7.99,NA,NA,321.05,200.11,487455139.04,1597904071.12,raptor,5.26
|
||||
arch-a,vgg8,throughput,2,batch,PASS,PASS,0.91,1.04,1100.00,965.00,429.31,482.60,407972136.20,541691691.00,raptor,1.14
|
||||
arch-a,vgg8,throughput,4,batch,PASS,PASS,0.78,1.04,1280.00,965.00,457.04,482.60,415761605.20,541691691.00,raptor,1.33
|
||||
arch-a,vgg8,throughput,8,batch,PASS,PASS,0.78,1.04,1290.00,965.00,464.07,482.60,396424007.70,541691691.00,raptor,1.34
|
||||
arch-a,resnet18,latency,1,element,PASS,FAIL,14.03,58.18,NA,NA,455.67,238.39,6394478521.12,13868866808.12,raptor,4.15
|
||||
arch-a,resnet18,throughput,2,batch,PASS,FAIL,11.73,13.46,85.20,74.30,503.11,481.60,6628073894.00,6563451004.00,raptor,1.15
|
||||
arch-a,resnet18,throughput,4,batch,PASS,FAIL,19.21,13.46,52.10,74.30,380.10,481.60,7957033431.00,6563451004.00,pimcomp,1.43
|
||||
arch-a,resnet18,throughput,8,batch,PASS,FAIL,19.93,13.46,50.20,74.30,361.80,481.60,7763795670.00,6563451004.00,pimcomp,1.48
|
||||
arch-a,resnet34,latency,1,element,PASS,FAIL,29.72,66.71,NA,NA,411.81,277.49,12238321706.68,18511425563.68,raptor,2.24
|
||||
arch-a,resnet34,throughput,2,batch,PASS,FAIL,23.27,23.58,43.00,42.40,471.72,515.68,13055037600.00,12283627410.00,raptor,1.01
|
||||
arch-a,resnet34,throughput,4,batch,PASS,FAIL,27.47,23.58,36.40,42.40,422.22,515.68,14899055210.00,12283627410.00,pimcomp,1.16
|
||||
arch-a,resnet34,throughput,8,batch,PASS,FAIL,39.84,23.58,25.10,42.40,352.28,515.68,17062658890.00,12283627410.00,pimcomp,1.69
|
||||
arch-a,googlenet,latency,1,element,PASS,PASS,9.78,62.92,NA,NA,563.87,231.19,5513262106.92,14547744283.24,raptor,6.44
|
||||
arch-a,googlenet,throughput,2,batch,PASS,FAIL,8.42,18.26,119.00,54.80,610.75,435.11,5777456331.00,8164787803.00,raptor,2.17
|
||||
arch-a,googlenet,throughput,4,batch,PASS,FAIL,10.73,18.26,93.20,54.80,528.03,435.11,6592892213.00,8164787803.00,raptor,1.70
|
||||
arch-a,googlenet,throughput,8,batch,PASS,FAIL,10.12,18.26,98.80,54.80,523.81,435.11,6521737843.00,8164787803.00,raptor,1.80
|
||||
arch-a,yolo11n,latency,1,element,PASS,FAIL,147.49,NA,NA,NA,290.17,NA,42797722129.00,NA,NA,NA
|
||||
arch-a,yolo11n,throughput,2,batch,PASS,FAIL,102.34,NA,9.77,NA,337.88,NA,38308060150.00,NA,NA,NA
|
||||
arch-a,yolo11n,throughput,4,batch,PASS,FAIL,87.91,NA,11.40,NA,358.20,NA,38729846260.00,NA,NA,NA
|
||||
arch-a,yolo11n,throughput,8,batch,PASS,FAIL,83.68,NA,11.90,NA,365.64,NA,38623589740.00,NA,NA,NA
|
||||
arch-b,vgg8,latency,1,element,PASS,PASS,1.53,7.15,NA,NA,292.56,173.77,448581679.04,1242814988.12,raptor,4.66
|
||||
arch-b,vgg8,throughput,2,batch,PASS,PASS,0.69,1.07,1460.00,932.00,456.04,409.47,403694238.30,439288835.80,raptor,1.57
|
||||
arch-b,vgg8,throughput,4,batch,PASS,PASS,0.67,1.07,1490.00,932.00,448.90,409.47,413526781.30,439288835.80,raptor,1.60
|
||||
arch-b,vgg8,throughput,8,batch,PASS,PASS,0.75,1.07,1330.00,932.00,425.74,409.47,394457860.80,439288835.80,raptor,1.43
|
||||
arch-b,resnet18,latency,1,element,PASS,FAIL,15.38,64.38,NA,NA,400.67,201.66,6160441550.12,12983432281.12,raptor,4.19
|
||||
arch-b,resnet18,throughput,2,batch,PASS,FAIL,12.12,13.43,82.50,74.40,461.41,484.80,6295321671.00,6569655551.00,raptor,1.11
|
||||
arch-b,resnet18,throughput,4,batch,PASS,FAIL,12.68,13.43,78.90,74.40,451.19,484.80,7096979122.00,6569655551.00,raptor,1.06
|
||||
arch-b,resnet18,throughput,8,batch,PASS,FAIL,20.59,13.43,48.60,74.40,321.63,484.80,7162570047.00,6569655551.00,pimcomp,1.53
|
||||
arch-b,resnet34,latency,1,element,PASS,FAIL,27.95,107.20,NA,NA,397.21,206.48,11103517952.68,22134174654.68,raptor,3.83
|
||||
arch-b,resnet34,throughput,2,batch,PASS,FAIL,22.28,22.85,44.90,43.80,438.24,493.33,14218683390.00,11392215660.00,raptor,1.03
|
||||
arch-b,resnet34,throughput,4,batch,PASS,FAIL,29.09,22.85,34.40,43.80,364.96,493.33,16328513230.00,11392215660.00,pimcomp,1.27
|
||||
arch-b,resnet34,throughput,8,batch,PASS,FAIL,47.68,22.85,21.00,43.80,289.82,493.33,16346419570.00,11392215660.00,pimcomp,2.09
|
||||
arch-b,googlenet,latency,1,element,PASS,PASS,11.22,37.94,NA,NA,483.27,242.04,5422781257.92,9182062336.24,raptor,3.38
|
||||
arch-b,googlenet,throughput,2,batch,PASS,FAIL,9.21,21.39,109.00,46.80,545.49,417.87,5593397699.00,9014726090.00,raptor,2.33
|
||||
arch-b,googlenet,throughput,4,batch,PASS,FAIL,8.56,21.39,117.00,46.80,555.26,417.87,5863121048.00,9014726090.00,raptor,2.50
|
||||
arch-b,googlenet,throughput,8,batch,PASS,FAIL,16.19,21.39,61.80,46.80,355.70,417.87,6726458739.00,9014726090.00,raptor,1.32
|
||||
arch-b,yolo11n,latency,1,element,PASS,FAIL,168.14,NA,NA,NA,245.31,NA,41248047514.00,NA,NA,NA
|
||||
arch-b,yolo11n,throughput,2,batch,PASS,FAIL,104.49,NA,9.57,NA,302.12,NA,35801517850.00,NA,NA,NA
|
||||
arch-b,yolo11n,throughput,4,batch,PASS,FAIL,108.25,NA,9.24,NA,297.48,NA,37629699620.00,NA,NA,NA
|
||||
arch-b,yolo11n,throughput,8,batch,PASS,FAIL,100.13,NA,9.99,NA,308.83,NA,37180785770.00,NA,NA,NA
|
||||
arch-a,vgg8,latency,1,element,PASS,PASS,1.51833,7.98507,NA,NA,321.046,200.111,4.87455e+08,1.5979e+09,raptor,5.26
|
||||
arch-a,vgg8,throughput,2,batch,PASS,PASS,0.906321,1.0364,1100,965,429.311,482.597,4.07972e+08,5.41692e+08,raptor,1.14
|
||||
arch-a,vgg8,throughput,4,batch,PASS,PASS,0.778242,1.0364,1280,965,457.039,482.597,4.15762e+08,5.41692e+08,raptor,1.33
|
||||
arch-a,vgg8,throughput,8,batch,PASS,PASS,0.778094,1.0364,1290,965,464.071,482.597,3.96424e+08,5.41692e+08,raptor,1.34
|
||||
arch-a,resnet18,latency,1,element,PASS,FAIL,14.0332,58.1775,NA,NA,455.67,238.389,6.3945e+09,1.38689e+10,raptor,4.15
|
||||
arch-a,resnet18,throughput,2,batch,PASS,FAIL,11.7314,13.4585,85.2,74.3,503.111,481.604,6.62807e+09,6.56345e+09,raptor,1.15
|
||||
arch-a,resnet18,throughput,4,batch,PASS,FAIL,19.2072,13.4585,52.1,74.3,380.1,481.604,7.95703e+09,6.56345e+09,pimcomp,1.43
|
||||
arch-a,resnet18,throughput,8,batch,PASS,FAIL,14.1578,13.4585,70.6,74.3,453.416,481.604,6.77909e+09,6.56345e+09,pimcomp,1.05
|
||||
arch-a,resnet34,latency,1,element,PASS,FAIL,29.7186,66.7114,NA,NA,411.806,277.485,1.22383e+10,1.85114e+10,raptor,2.24
|
||||
arch-a,resnet34,throughput,2,batch,PASS,FAIL,23.2705,23.5817,43,42.4,471.717,515.68,1.3055e+10,1.22836e+10,raptor,1.01
|
||||
arch-a,resnet34,throughput,4,batch,PASS,FAIL,27.468,23.5817,36.4,42.4,422.223,515.68,1.48991e+10,1.22836e+10,pimcomp,1.16
|
||||
arch-a,resnet34,throughput,8,batch,PASS,FAIL,39.8392,23.5817,25.1,42.4,352.28,515.68,1.70627e+10,1.22836e+10,pimcomp,1.69
|
||||
arch-a,googlenet,latency,1,element,PASS,PASS,9.77772,62.9248,NA,NA,563.864,231.193,5.5133e+09,1.45477e+10,raptor,6.44
|
||||
arch-a,googlenet,throughput,2,batch,PASS,FAIL,8.41545,18.2643,119,54.8,610.746,435.107,5.77746e+09,8.16479e+09,raptor,2.17
|
||||
arch-a,googlenet,throughput,4,batch,PASS,FAIL,10.7292,18.2643,93.2,54.8,528.026,435.107,6.59289e+09,8.16479e+09,raptor,1.70
|
||||
arch-a,googlenet,throughput,8,batch,PASS,FAIL,10.1219,18.2643,98.8,54.8,523.808,435.107,6.52174e+09,8.16479e+09,raptor,1.80
|
||||
arch-a,yolo11n,latency,1,element,PASS,FAIL,147.49,NA,NA,NA,290.174,NA,4.27977e+10,NA,NA,NA
|
||||
arch-a,yolo11n,throughput,2,batch,SKIPPED,FAIL,102.337,NA,9.77,NA,337.882,NA,3.83081e+10,NA,NA,NA
|
||||
arch-a,yolo11n,throughput,4,batch,SKIPPED,FAIL,87.9103,NA,11.4,NA,358.201,NA,3.87298e+10,NA,NA,NA
|
||||
arch-a,yolo11n,throughput,8,batch,SKIPPED,FAIL,82.9508,NA,12.1,NA,366.725,NA,3.85347e+10,NA,NA,NA
|
||||
arch-b,vgg8,latency,1,element,PASS,PASS,1.5333,7.15212,NA,NA,292.559,173.769,4.48582e+08,1.24281e+09,raptor,4.66
|
||||
arch-b,vgg8,throughput,2,batch,PASS,PASS,0.685556,1.07282,1460,932,456.044,409.47,4.03694e+08,4.39289e+08,raptor,1.57
|
||||
arch-b,vgg8,throughput,4,batch,PASS,PASS,0.673192,1.07282,1490,932,448.898,409.47,4.13527e+08,4.39289e+08,raptor,1.60
|
||||
arch-b,vgg8,throughput,8,batch,PASS,PASS,0.751106,1.07282,1330,932,425.742,409.47,3.94458e+08,4.39289e+08,raptor,1.43
|
||||
arch-b,resnet18,latency,1,element,PASS,FAIL,15.3758,64.3818,NA,NA,400.661,201.663,6.16048e+09,1.29834e+10,raptor,4.19
|
||||
arch-b,resnet18,throughput,2,batch,PASS,FAIL,12.1214,13.434,82.5,74.4,461.407,484.804,6.29532e+09,6.56966e+09,raptor,1.11
|
||||
arch-b,resnet18,throughput,4,batch,PASS,FAIL,11.3601,13.434,88,74.4,479.333,484.804,6.98745e+09,6.56966e+09,raptor,1.18
|
||||
arch-b,resnet18,throughput,8,batch,PASS,FAIL,13.1216,13.434,76.2,74.4,441.248,484.804,6.38271e+09,6.56966e+09,raptor,1.02
|
||||
arch-b,resnet34,latency,1,element,PASS,FAIL,27.9541,107.2,NA,NA,397.206,206.476,1.11035e+10,2.21342e+10,raptor,3.83
|
||||
arch-b,resnet34,throughput,2,batch,PASS,FAIL,22.282,22.8454,44.9,43.8,438.238,493.332,1.42187e+10,1.13922e+10,raptor,1.03
|
||||
arch-b,resnet34,throughput,4,batch,PASS,FAIL,29.0906,22.8454,34.4,43.8,364.956,493.332,1.63285e+10,1.13922e+10,pimcomp,1.27
|
||||
arch-b,resnet34,throughput,8,batch,PASS,FAIL,47.68,22.8454,21,43.8,289.821,493.332,1.63464e+10,1.13922e+10,pimcomp,2.09
|
||||
arch-b,googlenet,latency,1,element,PASS,PASS,11.2212,37.9367,NA,NA,483.266,242.037,5.42282e+09,9.18206e+09,raptor,3.38
|
||||
arch-b,googlenet,throughput,2,batch,PASS,FAIL,9.21209,21.3902,109,46.8,545.488,417.866,5.5934e+09,9.01473e+09,raptor,2.33
|
||||
arch-b,googlenet,throughput,4,batch,PASS,FAIL,8.56194,21.3902,117,46.8,555.264,417.866,5.86312e+09,9.01473e+09,raptor,2.50
|
||||
arch-b,googlenet,throughput,8,batch,PASS,FAIL,10.8652,21.3902,92,46.8,483.477,417.866,6.12683e+09,9.01473e+09,raptor,1.97
|
||||
arch-b,yolo11n,latency,1,element,PASS,FAIL,168.144,NA,NA,NA,245.314,NA,4.1248e+10,NA,NA,NA
|
||||
arch-b,yolo11n,throughput,2,batch,SKIPPED,FAIL,104.494,NA,9.57,NA,302.121,NA,3.58015e+10,NA,NA,NA
|
||||
arch-b,yolo11n,throughput,4,batch,SKIPPED,FAIL,108.247,NA,9.24,NA,297.484,NA,3.76297e+10,NA,NA,NA
|
||||
arch-b,yolo11n,throughput,8,batch,SKIPPED,FAIL,100.129,NA,9.99,NA,308.833,NA,3.71808e+10,NA,NA,NA
|
||||
|
||||
|
@@ -23,13 +23,13 @@ REPO = Path(__file__).resolve().parents[5]
|
||||
sys.path.insert(0, str(REPO / "validation"))
|
||||
|
||||
from raptor_validation.pimcomp_models import (
|
||||
FUNCTIONAL_MODELS,
|
||||
MODELS,
|
||||
SUITE,
|
||||
add_models_argument,
|
||||
)
|
||||
from raptor_validation.artifacts import artifacts_dir, remove_lock_files
|
||||
from raptor_validation.pimsim_nn import parse_pimsim_nn_metrics
|
||||
from raptor_validation.output_comparison import VALIDATION_POLICY
|
||||
from raptor_validation.validate_one import STAGE_COLORS
|
||||
|
||||
PIMCOMP_SOURCE = REPO / "third_party/PIMCOMP-NN"
|
||||
@@ -63,7 +63,7 @@ class ComparisonSpec:
|
||||
|
||||
|
||||
def model_dir(root: Path | None, name: str) -> Path:
|
||||
return root / name if root is not None else MODELS[name].parent
|
||||
return (root or SUITE) / name
|
||||
|
||||
|
||||
def result_dir(
|
||||
@@ -83,9 +83,9 @@ def result_dir(
|
||||
|
||||
|
||||
def common_dir(root: Path | None, name: str, common_root: Path | None = None) -> Path:
|
||||
suffix = "common" if FUNCTIONAL_MODELS[name] == MODELS[name] else "common-functional"
|
||||
base = common_root / name if common_root is not None else model_dir(root, name)
|
||||
return artifacts_dir(base) / suffix
|
||||
# Keep references that retain Softmax separate from historical outputs.
|
||||
return artifacts_dir(base) / ("common-full-model" if name in ("yolo11n", "googlenet") else "common")
|
||||
|
||||
|
||||
def clean_artifacts(
|
||||
@@ -199,6 +199,8 @@ def write_results_csv(
|
||||
}
|
||||
if report_path.exists():
|
||||
report = json.loads(report_path.read_text(encoding="utf-8"))
|
||||
if report.get("validation_policy") != VALIDATION_POLICY:
|
||||
raise ValueError(f"Report uses a different validation policy: {report_path}. Regenerate this comparison.")
|
||||
raptor_values = performance_values(report.get("raptor_performance") or {})
|
||||
pimcomp_values = performance_values(report.get("pimcomp_performance") or {})
|
||||
raptor_metric = raptor_values["throughput"] if comparison_mode == "throughput" else raptor_values["latency"]
|
||||
@@ -222,7 +224,11 @@ def write_results_csv(
|
||||
if raptor_metric is not None and pimcomp_metric is not None:
|
||||
min_metric = min(raptor_metric, pimcomp_metric)
|
||||
row.update(
|
||||
better_compiler=comparison_winner(comparison_mode, raptor_metric, pimcomp_metric),
|
||||
better_compiler=(
|
||||
comparison_winner(comparison_mode, raptor_metric, pimcomp_metric)
|
||||
if row["raptor_functional_validation"] == row["pimcomp_functional_validation"] == "PASS"
|
||||
else "NA"
|
||||
),
|
||||
speedup=(
|
||||
f"{max(raptor_metric, pimcomp_metric) / min_metric:.2f}"
|
||||
if min_metric > 0
|
||||
@@ -684,10 +690,9 @@ def main() -> int:
|
||||
)
|
||||
|
||||
missing = [
|
||||
str(path)
|
||||
str(MODELS[name])
|
||||
for name in args.models
|
||||
for path in (MODELS[name], FUNCTIONAL_MODELS[name])
|
||||
if not path.exists()
|
||||
if not MODELS[name].exists()
|
||||
]
|
||||
if missing:
|
||||
parser.error(f"missing model(s): {', '.join(missing)}")
|
||||
@@ -733,7 +738,7 @@ def main() -> int:
|
||||
try:
|
||||
run(
|
||||
prepare_common_command(
|
||||
FUNCTIONAL_MODELS[name],
|
||||
MODELS[name],
|
||||
common_dir(out_dir, name, common_root),
|
||||
args.timeout_seconds,
|
||||
reference_batch_size,
|
||||
@@ -770,7 +775,7 @@ def main() -> int:
|
||||
ComparisonSpec(
|
||||
label=label,
|
||||
model=MODELS[name],
|
||||
functional_model=FUNCTIONAL_MODELS[name],
|
||||
functional_model=MODELS[name],
|
||||
output_dir=model_result_dir,
|
||||
common_dir=common_dir(out_dir, name, common_root),
|
||||
config=configs[mode],
|
||||
|
||||
@@ -49,6 +49,13 @@ loaded provider tensors have no store producer; prefill reduces maximum
|
||||
reference error from about `6.70705` to `4.05e-6`. This reproduces Pimcomp's
|
||||
verifier contract; it does not repair the standalone batch program.
|
||||
|
||||
The reconstructed VGG8 batch program also loads intermediate Pool and Flatten
|
||||
buffers without corresponding store producers. Its output can remain zero.
|
||||
The small reference output can fit inside an absolute tolerance despite this
|
||||
missing computation. Comparison therefore requires a relative L2 check in
|
||||
addition to the elementwise check. A zero output against a nonzero reference
|
||||
has relative L2 error one and must fail the default relative tolerance.
|
||||
|
||||
## ResNet BatchNorm gap
|
||||
|
||||
Pimcomp's [frontend fusion](../../../../../third_party/PIMCOMP-NN/frontend/frontend.py#L535)
|
||||
|
||||
Reference in New Issue
Block a user