normalize names and artifact paths
This commit is contained in:
@@ -16,6 +16,7 @@ if str(VALIDATION_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(VALIDATION_DIR))
|
||||
|
||||
from raptor_validation.onnx_utils import _ONNX_TO_NP, onnx_io, write_inputs_binary, write_inputs_to_memory_bin
|
||||
from raptor_validation.artifacts import artifacts_dir
|
||||
from raptor_validation.validate_one import (
|
||||
MODE_COMPILE_ONLY,
|
||||
build_dump_ranges,
|
||||
@@ -75,10 +76,11 @@ def ensure_local_artifacts(args, model_path: Path):
|
||||
|
||||
|
||||
def ensure_existing_artifacts(model_dir: Path):
|
||||
artifact_root = artifacts_dir(model_dir)
|
||||
required_paths = [
|
||||
model_dir / "runner" / "build" / "runner",
|
||||
model_dir / "raptor" / "pim" / "config.json",
|
||||
model_dir / "raptor" / "pim" / "memory.bin",
|
||||
artifact_root / "runner" / "build" / "runner",
|
||||
artifact_root / "raptor" / "pim" / "config.json",
|
||||
artifact_root / "raptor" / "pim" / "memory.bin",
|
||||
]
|
||||
missing = [str(path) for path in required_paths if not path.exists()]
|
||||
if missing:
|
||||
@@ -185,13 +187,13 @@ def draw_classification_panel(image: Image.Image, results, output_path: Path):
|
||||
|
||||
|
||||
def run_reference_and_simulator(args, model_path: Path, tensor: np.ndarray):
|
||||
model_dir = model_path.parent
|
||||
runner_build_dir = model_dir / "runner" / "build"
|
||||
artifact_root = artifacts_dir(model_path.parent)
|
||||
runner_build_dir = artifact_root / "runner" / "build"
|
||||
runner_path = runner_build_dir / "runner"
|
||||
pim_dir = model_dir / "raptor" / "pim"
|
||||
simulation_dir = model_dir / "classification_demo" / "simulation"
|
||||
reference_dir = model_dir / "classification_demo" / "reference"
|
||||
inputs_dir = model_dir / "classification_demo" / "inputs"
|
||||
pim_dir = artifact_root / "raptor" / "pim"
|
||||
simulation_dir = artifact_root / "classification_demo" / "simulation"
|
||||
reference_dir = artifact_root / "classification_demo" / "reference"
|
||||
inputs_dir = artifact_root / "classification_demo" / "inputs"
|
||||
|
||||
simulation_dir.mkdir(parents=True, exist_ok=True)
|
||||
reference_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -222,7 +224,7 @@ def run_reference_and_simulator(args, model_path: Path, tensor: np.ndarray):
|
||||
subprocess.run(runner_cmd, cwd=runner_build_dir, check=True)
|
||||
|
||||
write_inputs_to_memory_bin(pim_dir / "memory.bin", pim_dir / "config.json", [tensor])
|
||||
input_bin_path = simulation_dir / "input.bin"
|
||||
input_bin_path = simulation_dir / "input_0.bin"
|
||||
write_inputs_binary(input_bin_path, [tensor])
|
||||
dump_ranges = build_dump_ranges(pim_dir / "config.json", output_descriptors)
|
||||
output_bin_path = simulation_dir / "out.bin"
|
||||
@@ -232,7 +234,8 @@ def run_reference_and_simulator(args, model_path: Path, tensor: np.ndarray):
|
||||
output_bin_path,
|
||||
dump_ranges,
|
||||
timeout_sec=args.command_timeout_seconds,
|
||||
input_paths=[input_bin_path],
|
||||
input_dir=input_bin_path.parent,
|
||||
batch_size=1,
|
||||
)
|
||||
|
||||
output_index, output_name, output_dtype_code, output_shape = output_descriptors[0]
|
||||
|
||||
@@ -37,55 +37,55 @@ class Tile:
|
||||
|
||||
|
||||
TILES = (
|
||||
Tile("classic-reference", "REFERENCE", "Classic Conv + exact weight unfolding",
|
||||
Tile("classic-reference", "Reference", "Classic Conv + exact weight unfolding",
|
||||
"Original OIHW weights; the two implementations choose different K orders.",
|
||||
"reference", "Y[p,o] = Σc,kh,kw Xpatch[p,c,kh,kw] · W[o,c,kh,kw]",
|
||||
"At every output position, multiply the patch by one filter and add every product."),
|
||||
Tile("pimcomp-element", "PIMCOMP", "Element pipeline",
|
||||
Tile("pimcomp-element", "Pimcomp", "Element pipeline",
|
||||
"One patch vector per input cycle; mapped weights stay fixed.",
|
||||
"pimcomp_element", "patchPIM[1×K] · WflatPIM[K×O] → Yp[1×O]",
|
||||
"Keep Wflat in the arrays; stream one patch each cycle to produce all O outputs."),
|
||||
Tile("pimcomp-batch", "PIMCOMP", "Batch / replicated pipeline",
|
||||
Tile("pimcomp-batch", "Pimcomp", "Batch / replicated pipeline",
|
||||
"Complete Wflat copies divide patches or input samples.",
|
||||
"pimcomp_batch", "for replica r: Yr = patchr[1×K] · WflatPIM[K×O]",
|
||||
"Copy all weights R times and send different patches to the copies in parallel."),
|
||||
Tile("raptor-legacy-im2col", "RAPTOR", "Legacy explicit im2col",
|
||||
Tile("raptor-legacy-im2col", "Raptor", "Legacy explicit im2col",
|
||||
"Every patch becomes one row of a global P×K matrix.",
|
||||
"legacy", "Y[P×O] = im2col(X)[P×K] · WflatR[K×O]",
|
||||
"Write every image patch as one matrix row, then multiply the two large matrices."),
|
||||
Tile("raptor-packed-im2col", "RAPTOR", "Packed im2col",
|
||||
Tile("raptor-packed-im2col", "Raptor", "Packed im2col",
|
||||
"Pack q patch rows and repeat Wflat on a block diagonal.",
|
||||
"packed", "packedY[1×qO] = [patch0|…|patchq−1] · diag(WflatR,…,WflatR)",
|
||||
"Join q patches and use diagonal weight copies so one multiply computes q independent outputs."),
|
||||
Tile("raptor-streamed-patch", "RAPTOR", "Streamed patch",
|
||||
Tile("raptor-streamed-patch", "Raptor", "Streamed patch",
|
||||
"Gather one patch into bounded scratch; avoid global im2col.",
|
||||
"streamed_patch", "Yp[1×O] = scratchPatchp[1×K] · WflatR[K×O]",
|
||||
"Gather one patch, multiply it, write its output, and reuse scratch for the next patch."),
|
||||
Tile("raptor-streamed-packed", "RAPTOR", "Streamed packed",
|
||||
Tile("raptor-streamed-packed", "Raptor", "Streamed packed",
|
||||
"Gather q patch rows in bounded scratch, then block-diagonal pack them.",
|
||||
"streamed_packed", "packedY = packedScratch[1×qK] · diag(WflatR×q)[qK×qO]",
|
||||
"Gather q patches in small scratch, join them, multiply by diagonal weights, then unpack q outputs."),
|
||||
Tile("raptor-depthwise", "RAPTOR", "Depthwise special case",
|
||||
Tile("raptor-depthwise", "Raptor", "Depthwise special case",
|
||||
"Each channel owns one row-major 3×3 kernel; channels never reduce together.",
|
||||
"depthwise", "Y[p,c] = Σkh,kw Xpatch[p,c,kh,kw] · W[c,kh,kw]",
|
||||
"For each channel separately, multiply its nine patch values by its nine weights and add."),
|
||||
Tile("raptor-output-channel-tiled", "RAPTOR", "Output-channel tiled",
|
||||
Tile("raptor-output-channel-tiled", "Raptor", "Output-channel tiled",
|
||||
"Every O tile retains all channel-major K rows and selects output columns.",
|
||||
"c_tiled", "Y[:,Oj] = patch[1×K] · Wflat[:,Oj][K×|Oj|]; concat j",
|
||||
"Reuse the full patch for each output-filter group, then join the output groups."),
|
||||
Tile("raptor-input-k-tiled", "RAPTOR", "Input-K tiled",
|
||||
Tile("raptor-input-k-tiled", "Raptor", "Input-K tiled",
|
||||
"Split matching K ranges; add their partial output vectors.",
|
||||
"k_tiled", "Y[1×O] = Σi patch[Ki] · Wflat[Ki,:]",
|
||||
"Multiply matching K slices independently, then add their partial output vectors."),
|
||||
Tile("raptor-tiled-2d", "RAPTOR", "Two-dimensional tiled",
|
||||
Tile("raptor-tiled-2d", "Raptor", "Two-dimensional tiled",
|
||||
"Partition both K rows and output-filter columns.",
|
||||
"tiled_2d", "Y[:,Oj] = Σi patch[Ki] · Wflat[Ki,Oj]; concat j",
|
||||
"Split both directions: add results down K and join results across output groups."),
|
||||
Tile("raptor-row-strip", "RAPTOR", "Pixel-major row-strip",
|
||||
Tile("raptor-row-strip", "Raptor", "Pixel-major row-strip",
|
||||
"A lane forms patches across one output row and slices K.",
|
||||
"row_strip", "for x: Y[r,x,:] = Σi patch[r,x,Ki] · Wflat[Ki,:]",
|
||||
"Move across one output row; at each x form a patch, multiply its K slices, and add."),
|
||||
Tile("raptor-row-strip-c-tiled", "RAPTOR", "Row-strip + output tiling",
|
||||
Tile("raptor-row-strip-c-tiled", "Raptor", "Row-strip + output tiling",
|
||||
"Each row lane is duplicated across disjoint output-column tiles.",
|
||||
"row_strip_c", "for x,j: Y[r,x,Oj] = patch[r,x,:] · Wflat[:,Oj]",
|
||||
"Give each output-filter group a copy of the row lane, then join their output columns."),
|
||||
@@ -473,7 +473,7 @@ def operation_scene(d: Drawio, parent: str, scene: str) -> None:
|
||||
def reference_body(d: Drawio, parent: str) -> None:
|
||||
add_box(d, parent, 24, 252, 712, 158, "", fill="#ffffff", stroke=PIMCOMP)
|
||||
add_text(d, parent, 40, 260, 680, 22,
|
||||
"PIMCOMP: spatial-major, row-wise positions; C interleaved", 11,
|
||||
"Pimcomp: Spatial-major, row-wise positions; C interleaved", 11,
|
||||
color=PIMCOMP, align="center", bold=True)
|
||||
order_vector(d, parent, 306, "pimcomp", weight=False, label="Input patch")
|
||||
order_vector(d, parent, 366, "pimcomp", weight=True, label="Matching W")
|
||||
@@ -483,7 +483,7 @@ def reference_body(d: Drawio, parent: str) -> None:
|
||||
|
||||
add_box(d, parent, 24, 424, 712, 158, "", fill="#ffffff", stroke=RAPTOR)
|
||||
add_text(d, parent, 40, 432, 680, 22,
|
||||
"RAPTOR: channel-major; each 3×3 plane is row-major", 11,
|
||||
"Raptor: channel-major; each 3×3 plane is row-major", 11,
|
||||
color=RAPTOR, align="center", bold=True)
|
||||
order_vector(d, parent, 478, "raptor", weight=False, label="Input patch")
|
||||
order_vector(d, parent, 538, "raptor", weight=True, label="Matching W")
|
||||
@@ -500,22 +500,22 @@ def reference_body(d: Drawio, parent: str) -> None:
|
||||
def layout_reference(d: Drawio, parent: str, tile: Tile, accent: str) -> None:
|
||||
if tile.scene == "depthwise":
|
||||
layout = "independent row-major Kc=9 per channel"
|
||||
elif tile.owner == "PIMCOMP":
|
||||
layout = "PIMCOMP spatial-major K order"
|
||||
elif tile.owner == "Pimcomp":
|
||||
layout = "Pimcomp Spatial-major K order"
|
||||
else:
|
||||
layout = "RAPTOR channel-major K order"
|
||||
layout = "Raptor channel-major K order"
|
||||
add_box(d, parent, 24, 140, 712, 42, "", fill=PALE, stroke=accent)
|
||||
add_text(d, parent, 38, 146, 684, 30,
|
||||
f"LAYOUT → see REFERENCE tile: {layout}", 10,
|
||||
f"Layout → see Reference tile: {layout}", 10,
|
||||
color=accent, align="center", bold=True)
|
||||
|
||||
|
||||
def algorithm_card(d: Drawio, parent: str, tile: Tile, accent: str) -> None:
|
||||
add_box(d, parent, 24, 638, 712, 102, "", fill="#ffffff", stroke=accent)
|
||||
add_text(d, parent, 40, 646, 90, 34, "ALGORITHM", 9,
|
||||
add_text(d, parent, 40, 646, 90, 34, "Algorithm", 9,
|
||||
color=accent, bold=True)
|
||||
add_text(d, parent, 132, 644, 588, 38, tile.algorithm, 10)
|
||||
add_text(d, parent, 40, 690, 90, 34, "MATH", 9,
|
||||
add_text(d, parent, 40, 690, 90, 34, "Math", 9,
|
||||
color=accent, bold=True)
|
||||
add_text(d, parent, 132, 686, 588, 42, tile.formula, 10,
|
||||
color=accent, bold=True)
|
||||
@@ -524,7 +524,7 @@ def algorithm_card(d: Drawio, parent: str, tile: Tile, accent: str) -> None:
|
||||
def render_tile(d: Drawio, tile: Tile, index: int) -> None:
|
||||
col, row = index % COLS, index // COLS
|
||||
parent = d.group(col * (TILE + GAP), row * (TILE + GAP), tile.slug)
|
||||
accent = {"REFERENCE": REFERENCE, "PIMCOMP": PIMCOMP, "RAPTOR": RAPTOR}[tile.owner]
|
||||
accent = {"Reference": REFERENCE, "Pimcomp": PIMCOMP, "Raptor": RAPTOR}[tile.owner]
|
||||
add_box(d, parent, 0, 0, TILE, TILE, "", fill="#fbfcff", stroke=accent,
|
||||
stroke_width=3)
|
||||
add_box(d, parent, 24, 20, 106, 28, tile.owner, fill=accent, stroke=accent,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Raptor compiler ablation
|
||||
|
||||
`run_ablation.py` performs the complete synchronization/Spatial-planning
|
||||
ablation study on the Pimcomp model suite. By default it runs `vgg8`,
|
||||
`resnet18`, `resnet34`, and `googlenet` across `arch-a` and `arch-b`, latency,
|
||||
and throughput pipeline 4. `arch-c` and `yolo11n` are run only when selected
|
||||
explicitly.
|
||||
Latency is pipeline 1; the wrapper invokes the suite runner separately for
|
||||
latency and throughput pipeline 4.
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/ablation/run_ablation.py \
|
||||
--jobs 4
|
||||
```
|
||||
|
||||
## Variants
|
||||
|
||||
| Variant | Raptor options |
|
||||
|---|---|
|
||||
| `baseline` | None. |
|
||||
| `no-synchronization` | `--pim-disable-synchronization` |
|
||||
| `no-spatial-planning` | `--pim-disable-spatial-planning` |
|
||||
| `no-synchronization-no-spatial-planning` | Both options. |
|
||||
|
||||
Every variant runs Raptor only. Pimcomp is not compiled, validated, or
|
||||
simulated. Reference inputs and outputs are generated once under the shared
|
||||
common-artifact root and reused by every variant. Ctrl+C terminates the active
|
||||
variant and all of its worker jobs.
|
||||
|
||||
The percentage baseline is `no-synchronization-no-spatial-planning`: both
|
||||
ablation features are disabled, so its values are `+0.00%`. Every other
|
||||
variant reports the signed percentage difference of its Raptor metrics from
|
||||
that reference for the same model, architecture, mode, and pipeline. Positive
|
||||
values mean the metric is higher; negative values mean it is lower.
|
||||
|
||||
## Options
|
||||
|
||||
| Option | Description and default |
|
||||
|---|---|
|
||||
| `-h`, `--help` | Show help and exit. |
|
||||
| `--out-dir PATH` | Suite root. Default: `validation/networks/pimcomp_models`. Model artifacts are stored below `<out-dir>/<model>/artifacts`; disabled variants use `<arch>/<mode>[/pipelineN]/ablation/<variant>`. Variant summaries remain under `<out-dir>/ablation/<variant>/`. |
|
||||
| `--models MODEL [...]` | Models to run. Default: `vgg8 resnet18 resnet34 googlenet`; include `yolo11n` explicitly when needed. |
|
||||
| `--variant NAME` | Select a variant from the table above; repeat for multiple variants. Default: all variants. The feature-full baseline and percentage reference are added automatically when needed. |
|
||||
| `--dry-run` | Print the suite-runner commands that would run without modifying files. Default: off. |
|
||||
|
||||
All options of
|
||||
[`run_pimcomp_models.py`](../pimcomp/compare/README.md), including
|
||||
`--archs`, `--mode`, `--pipeline`, `--no-fast`, and
|
||||
`--raptor-extra-arg=ARG`, are forwarded to each variant. `--out-dir`,
|
||||
`--variant`, and `--dry-run` belong to this wrapper; `--only` is reserved for
|
||||
the suite runner's comparison mode and is rejected by this wrapper, which
|
||||
always invokes `--raptor-only`. If neither `--mode` nor `--pipeline` is
|
||||
forwarded, the wrapper uses its default latency and throughput/pipeline-4
|
||||
case set. Supplying either option overrides that default and is passed through
|
||||
as one suite-runner invocation per variant.
|
||||
|
||||
The feature-full `baseline` variant uses the normal `run_pimcomp_models.py`
|
||||
artifact paths and is rerun with the same selected cases and forwarded options
|
||||
as the disabled variants. The three disabled variants are stored below each model's
|
||||
`artifacts/<arch>/<mode>[/pipelineN]/ablation/` directory. Shared reference
|
||||
artifacts remain under each model's `artifacts/common` directory. Transient
|
||||
per-variant comparison summaries are removed after aggregation. The combined
|
||||
table is written to `<out-dir>/results_ablation.csv`; it contains only the variant, case
|
||||
identifiers, and signed `latency_percent`, `throughput_percent`,
|
||||
`power_percent`, and `energy_percent` columns. These are Raptor metrics;
|
||||
Pimcomp metrics are omitted because the ablation invokes Raptor only.
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the synchronization and Spatial-planning ablation matrix on Pimcomp models."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import math
|
||||
import os
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parents[4]
|
||||
RUNNER = REPO / "validation/tools/pim/pimcomp/compare/run_pimcomp_models.py"
|
||||
DEFAULT_OUT_DIR = REPO / "validation/networks/pimcomp_models"
|
||||
sys.path.insert(0, str(REPO / "validation"))
|
||||
|
||||
from raptor_validation.pimcomp_models import (
|
||||
ABLATION_DEFAULT_MODELS,
|
||||
add_models_argument,
|
||||
)
|
||||
from raptor_validation.artifacts import remove_lock_files
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Variant:
|
||||
name: str
|
||||
raptor_args: tuple[str, ...]
|
||||
|
||||
|
||||
VARIANTS = (
|
||||
Variant("baseline", ()),
|
||||
Variant("no-synchronization", ("--pim-disable-synchronization",)),
|
||||
Variant("no-spatial-planning", ("--pim-disable-spatial-planning",)),
|
||||
Variant(
|
||||
"no-synchronization-no-spatial-planning",
|
||||
("--pim-disable-synchronization", "--pim-disable-spatial-planning"),
|
||||
),
|
||||
)
|
||||
VARIANT_BY_NAME = {variant.name: variant for variant in VARIANTS}
|
||||
REFERENCE_VARIANT = "no-synchronization-no-spatial-planning"
|
||||
COMPARISON_RESULTS_FILENAME = "results_comparison.csv"
|
||||
ABLATION_RESULTS_FILENAME = "results_ablation.csv"
|
||||
CASE_FIELDS = ("arch", "model", "mode", "raptor_pipeline")
|
||||
PERCENTAGE_FIELDS = (
|
||||
("raptor_latency_ms", "latency_percent"),
|
||||
("raptor_throughput_samples_s", "throughput_percent"),
|
||||
("raptor_power_mw", "power_percent"),
|
||||
("raptor_energy_pj", "energy_percent"),
|
||||
)
|
||||
RESULT_FIELDS = (*CASE_FIELDS, *(target for _, target in PERCENTAGE_FIELDS))
|
||||
DEFAULT_CASE_ARGUMENTS = (
|
||||
("latency", ("--mode", "latency")),
|
||||
("throughput/pipeline4", ("--mode", "throughput", "--pipeline", "4")),
|
||||
)
|
||||
DEFAULT_CASE_KEYS = frozenset({("latency", "1"), ("throughput", "4")})
|
||||
|
||||
|
||||
def parse_args(argv: list[str] | None = None) -> tuple[argparse.Namespace, list[str]]:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run the complete compiler ablation matrix on the Pimcomp model suite.",
|
||||
allow_abbrev=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out-dir",
|
||||
type=Path,
|
||||
default=DEFAULT_OUT_DIR,
|
||||
help="Artifact root (default: validation/networks/pimcomp_models).",
|
||||
)
|
||||
add_models_argument(parser, ABLATION_DEFAULT_MODELS)
|
||||
parser.add_argument(
|
||||
"--variant",
|
||||
choices=tuple(VARIANT_BY_NAME),
|
||||
action="append",
|
||||
dest="variants",
|
||||
help="Run only this variant; baseline is added when needed. Repeat as needed.",
|
||||
)
|
||||
parser.add_argument("--dry-run", action="store_true", help="Print runner commands without modifying files.")
|
||||
args, forwarded = parser.parse_known_args(argv)
|
||||
if any(option == "--only" or option.startswith("--only=") for option in forwarded):
|
||||
parser.error("--only is not supported; the ablation wrapper always runs Raptor only")
|
||||
return args, forwarded
|
||||
|
||||
|
||||
def selected_variants(names: list[str] | None) -> list[Variant]:
|
||||
requested = set(names or VARIANT_BY_NAME)
|
||||
requested.add("baseline")
|
||||
requested.add(REFERENCE_VARIANT)
|
||||
return [variant for variant in VARIANTS if variant.name in requested]
|
||||
|
||||
|
||||
def has_option(arguments: list[str], option: str) -> bool:
|
||||
return any(argument == option or argument.startswith(f"{option}=") for argument in arguments)
|
||||
|
||||
|
||||
def runner_argument_sets(forwarded: list[str]) -> tuple[tuple[str, list[str]], ...]:
|
||||
if has_option(forwarded, "--mode") or has_option(forwarded, "--pipeline"):
|
||||
return (("requested", forwarded),)
|
||||
return tuple(
|
||||
(label, [*forwarded, *case_arguments])
|
||||
for label, case_arguments in DEFAULT_CASE_ARGUMENTS
|
||||
)
|
||||
|
||||
|
||||
def variant_output_dir(out_dir: Path, variant: Variant) -> Path:
|
||||
return out_dir if variant.name == "baseline" else out_dir / "ablation" / variant.name
|
||||
|
||||
|
||||
def remove_variant_summaries(out_dir: Path) -> None:
|
||||
summary_root = out_dir / "ablation"
|
||||
if not summary_root.is_dir():
|
||||
return
|
||||
for summary in summary_root.glob("*/results_comparison.csv"):
|
||||
summary.unlink()
|
||||
for variant_dir in summary_root.iterdir():
|
||||
if variant_dir.is_dir() and not any(variant_dir.iterdir()):
|
||||
variant_dir.rmdir()
|
||||
if not any(summary_root.iterdir()):
|
||||
summary_root.rmdir()
|
||||
|
||||
|
||||
def runner_command(
|
||||
out_dir: Path,
|
||||
variant: Variant,
|
||||
models: list[str],
|
||||
forwarded: list[str],
|
||||
common_root: Path,
|
||||
dry_run: bool,
|
||||
) -> list[str]:
|
||||
command = [
|
||||
sys.executable,
|
||||
str(RUNNER),
|
||||
"--out-dir",
|
||||
str(out_dir),
|
||||
"--raptor-only",
|
||||
"--models",
|
||||
*models,
|
||||
]
|
||||
if variant.name != "baseline":
|
||||
command.extend(("--ablation-variant", variant.name))
|
||||
if not has_option(forwarded, "--common-dir"):
|
||||
command.extend(("--common-dir", str(common_root)))
|
||||
command.extend(forwarded)
|
||||
command.extend(f"--raptor-extra-arg={arg}" for arg in variant.raptor_args)
|
||||
if dry_run:
|
||||
command.append("--dry-run")
|
||||
return command
|
||||
|
||||
|
||||
def terminate_process_group(process: subprocess.Popen[bytes]) -> None:
|
||||
if process.poll() is not None:
|
||||
return
|
||||
try:
|
||||
os.killpg(process.pid, signal.SIGTERM)
|
||||
except ProcessLookupError:
|
||||
return
|
||||
try:
|
||||
process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
try:
|
||||
os.killpg(process.pid, signal.SIGKILL)
|
||||
except ProcessLookupError:
|
||||
return
|
||||
process.wait()
|
||||
|
||||
|
||||
def percentage_difference(value: str | None, reference: str | None) -> str:
|
||||
try:
|
||||
current = float(value) if value is not None else math.nan
|
||||
baseline = float(reference) if reference is not None else math.nan
|
||||
except ValueError:
|
||||
return "NA"
|
||||
if not math.isfinite(current) or not math.isfinite(baseline) or baseline == 0:
|
||||
return "NA"
|
||||
return f"{(current - baseline) / baseline * 100:+.2f}%"
|
||||
|
||||
|
||||
def aggregate_results(
|
||||
out_dir: Path,
|
||||
variants: list[Variant],
|
||||
selected_cases: frozenset[tuple[str, str]] | None = None,
|
||||
selected_models: frozenset[str] | None = None,
|
||||
) -> tuple[Path | None, list[str]]:
|
||||
fields: list[str] | None = None
|
||||
rows_by_variant: dict[str, list[dict[str, str]]] = {}
|
||||
failures = []
|
||||
for variant in variants:
|
||||
results_path = variant_output_dir(out_dir, variant) / COMPARISON_RESULTS_FILENAME
|
||||
if not results_path.is_file():
|
||||
failures.append(f"{variant.name}: missing {results_path}")
|
||||
continue
|
||||
with results_path.open(newline="", encoding="utf-8") as stream:
|
||||
reader = csv.DictReader(stream)
|
||||
if reader.fieldnames is None:
|
||||
failures.append(f"{variant.name}: empty {results_path}")
|
||||
continue
|
||||
if fields is None:
|
||||
fields = reader.fieldnames
|
||||
elif reader.fieldnames != fields:
|
||||
failures.append(f"{variant.name}: inconsistent columns in {results_path}")
|
||||
continue
|
||||
selected_rows = rows_by_variant.setdefault(variant.name, [])
|
||||
for row in reader:
|
||||
if selected_models is not None and row.get("model") not in selected_models:
|
||||
continue
|
||||
if selected_cases is not None and (
|
||||
row.get("mode"), row.get("raptor_pipeline")
|
||||
) not in selected_cases:
|
||||
continue
|
||||
selected_rows.append(row)
|
||||
if fields is None:
|
||||
return None, failures
|
||||
reference_rows = {
|
||||
tuple(row.get(field, "") for field in CASE_FIELDS): row
|
||||
for row in rows_by_variant.get(REFERENCE_VARIANT, [])
|
||||
}
|
||||
if REFERENCE_VARIANT not in rows_by_variant:
|
||||
failures.append(f"{REFERENCE_VARIANT}: missing percentage reference results")
|
||||
output = out_dir / ABLATION_RESULTS_FILENAME
|
||||
with output.open("w", newline="", encoding="utf-8") as stream:
|
||||
writer = csv.DictWriter(stream, fieldnames=("variant", *RESULT_FIELDS), lineterminator="\n")
|
||||
writer.writeheader()
|
||||
for variant in variants:
|
||||
for row in rows_by_variant.get(variant.name, []):
|
||||
reference = reference_rows.get(tuple(row.get(field, "") for field in CASE_FIELDS))
|
||||
writer.writerow(
|
||||
{
|
||||
"variant": variant.name,
|
||||
**{field: row.get(field, "") for field in CASE_FIELDS},
|
||||
**{
|
||||
target: percentage_difference(
|
||||
row.get(source), reference.get(source) if reference else None
|
||||
)
|
||||
for source, target in PERCENTAGE_FIELDS
|
||||
},
|
||||
}
|
||||
)
|
||||
return output, failures
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args, forwarded = parse_args(argv)
|
||||
if not RUNNER.is_file():
|
||||
print(f"Missing Pimcomp runner: {RUNNER}", file=sys.stderr)
|
||||
return 1
|
||||
variants = selected_variants(args.variants)
|
||||
out_dir = args.out_dir.resolve()
|
||||
case_sets = runner_argument_sets(forwarded)
|
||||
if args.dry_run:
|
||||
for variant in variants:
|
||||
for _, case_forwarded in case_sets:
|
||||
print(
|
||||
shlex.join(
|
||||
runner_command(
|
||||
out_dir,
|
||||
variant,
|
||||
args.models,
|
||||
case_forwarded,
|
||||
out_dir,
|
||||
True,
|
||||
)
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
failed = []
|
||||
current_process = None
|
||||
try:
|
||||
for variant in variants:
|
||||
for case_label, case_forwarded in case_sets:
|
||||
command = runner_command(
|
||||
out_dir, variant, args.models, case_forwarded, out_dir, False
|
||||
)
|
||||
print(f"[{variant.name}/{case_label}] {shlex.join(command)}")
|
||||
current_process = subprocess.Popen(
|
||||
command,
|
||||
cwd=REPO,
|
||||
start_new_session=True,
|
||||
)
|
||||
returncode = current_process.wait()
|
||||
current_process = None
|
||||
if returncode:
|
||||
failed.append(f"{variant.name}/{case_label}: runner exited with {returncode}")
|
||||
except KeyboardInterrupt:
|
||||
if current_process is not None:
|
||||
terminate_process_group(current_process)
|
||||
remove_lock_files(out_dir)
|
||||
remove_variant_summaries(out_dir)
|
||||
print("Interrupted; terminated the active ablation job.", file=sys.stderr)
|
||||
return 130
|
||||
|
||||
remove_lock_files(out_dir)
|
||||
selected_cases = DEFAULT_CASE_KEYS if len(case_sets) > 1 else None
|
||||
output, aggregation_failures = aggregate_results(
|
||||
out_dir,
|
||||
variants,
|
||||
selected_cases,
|
||||
frozenset(args.models),
|
||||
)
|
||||
remove_variant_summaries(out_dir)
|
||||
failed.extend(aggregation_failures)
|
||||
if output is not None:
|
||||
print(f"Ablation results: {output}")
|
||||
if failed:
|
||||
print("Failed: " + "; ".join(failed), file=sys.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,62 @@
|
||||
# Pimcomp model comparison
|
||||
|
||||
These scripts compare Raptor with Pimcomp on the models in
|
||||
`validation/networks/pimcomp_models`.
|
||||
|
||||
`run_pimcomp_models.py` runs the supported model suite across the configured
|
||||
architectures and simulation modes. `compare_raptor_pimcomp_model.py` is the
|
||||
lower-level one-model comparison used by the suite runner.
|
||||
|
||||
## Suite runner
|
||||
|
||||
Run all five models, Arch-A and Arch-B, latency, and throughput:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/run_pimcomp_models.py
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Description and default |
|
||||
|---|---|
|
||||
| `-h`, `--help` | Show help and exit. |
|
||||
| `--out-dir PATH` | Suite root; artifacts are placed below `<out-dir>/<model>/artifacts/`. Default: `validation/networks/pimcomp_models`. |
|
||||
| `--common-dir PATH` | Shared root for per-model reference artifacts. Default: `<out-dir>/<model>/artifacts/common`. |
|
||||
| `--models MODEL [...]` | Models to run: `vgg8`, `resnet18`, `resnet34`, `googlenet`, `yolo11n`. Default: all five; pass a subset to select specific models. |
|
||||
| `--archs ARCH [...]` | Pimcomp hardware profiles to run. Default: `arch-a arch-b`. |
|
||||
| `--mode {latency,throughput} [...]` | Simulation modes. Default: both. |
|
||||
| `--only {raptor,pimcomp}` | Re-run only one compiler side and reuse the other side's existing report. Default: run both sides. |
|
||||
| `--raptor-only` | Run Raptor without compiling, validating, or simulating Pimcomp. Default: off. |
|
||||
| `--pipeline {1,2,4,8}` | Select one Raptor pipeline. Default: latency pipeline 1 and throughput pipelines 2, 4, and 8. |
|
||||
| `--pimsim-time-ms INT` | Throughput convergence deadline. Default: `1000`. |
|
||||
| `--batch-size INT` | Functional throughput batch size. Default: `128`. |
|
||||
| `--timeout-seconds FLOAT` | Per-stage timeout; `0` means unlimited. Default: `0`. |
|
||||
| `-j INT`, `--jobs INT` | Parallel comparison workers. Default: `4`. |
|
||||
| `--clean` | Remove generated comparison artifacts and summaries, then exit. Default: off. |
|
||||
| `--ablation-variant NAME` | Put generated artifacts below `<mode>[/pipelineN]/ablation/NAME` and write the summary below `ablation/NAME`. Default: none. |
|
||||
| `--dry-run` | Print commands without modifying files. Default: off. |
|
||||
| `--no-fast` | Disable fast throughput convergence. Default: off. |
|
||||
| `--raptor-extra-arg=ARG` | Extra Raptor compiler argument; repeat for multiple arguments. Default: none. |
|
||||
|
||||
Arguments beginning with `--` must use the equals form when passed through:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/run_pimcomp_models.py \
|
||||
--models vgg8 \
|
||||
--raptor-extra-arg=--pim-disable-synchronization
|
||||
```
|
||||
|
||||
The runner writes `results_comparison.csv` under the selected result root. It reuses
|
||||
shared model inputs, outputs, and reference runners, and reuses Pimcomp
|
||||
artifacts between pipelines in the same model/architecture/mode group. With
|
||||
`--raptor-only`, only Raptor results are generated and Pimcomp is not invoked.
|
||||
All generated files are kept below each model's `artifacts/` directory;
|
||||
`--clean` also removes any `.lock` files left by an interrupted reference
|
||||
generation.
|
||||
|
||||
## One-model comparator
|
||||
|
||||
`compare_raptor_pimcomp_model.py` compares one ONNX model. Its required
|
||||
arguments are `--model PATH` and `--out-dir PATH`; use `--help` for the full
|
||||
lower-level interface. The suite runner supplies the model, hardware profile,
|
||||
simulation mode, and reuse paths automatically.
|
||||
+243
-150
@@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import fcntl
|
||||
import gzip
|
||||
import importlib.util
|
||||
import json
|
||||
@@ -30,8 +31,8 @@ PIMSIM_CONFIG_DIR = VALIDATION_DIR / "pimsim_configs/pimcomp"
|
||||
PIMCOMP_OUTPUT_FILES = ("SimulationInfo.gz", "VerificationInfo.json", "MappingResult.txt")
|
||||
sys.path.insert(0, str(VALIDATION_DIR))
|
||||
|
||||
from raptor_validation.gen_network_runner import gen_network_runner # noqa: E402
|
||||
from raptor_validation.onnx_utils import ( # noqa: E402
|
||||
from raptor_validation.gen_network_runner import gen_network_runner
|
||||
from raptor_validation.onnx_utils import (
|
||||
_ONNX_TO_NP,
|
||||
generate_input_batch,
|
||||
gen_random_inputs,
|
||||
@@ -41,12 +42,13 @@ from raptor_validation.onnx_utils import ( # noqa: E402
|
||||
write_input_batch_csv,
|
||||
write_inputs_to_memory_bin,
|
||||
)
|
||||
from raptor_validation.raptor import compile_with_raptor # noqa: E402
|
||||
from raptor_validation.pimsim_nn import ( # noqa: E402
|
||||
from raptor_validation.raptor import compile_with_raptor
|
||||
from raptor_validation.pimsim_nn import (
|
||||
export_raptor_pimsim_artifact,
|
||||
parse_pimsim_nn_metrics,
|
||||
)
|
||||
from raptor_validation.validate_one import ( # noqa: E402
|
||||
from raptor_validation.artifacts import runner_uses_library
|
||||
from raptor_validation.validate_one import (
|
||||
STAGE_COLORS,
|
||||
build_pim_simulator_command,
|
||||
build_dump_ranges,
|
||||
@@ -99,7 +101,7 @@ def print_step(
|
||||
stage: str | None = None,
|
||||
):
|
||||
color = STAGE_COLORS.get(stage or name, Fore.WHITE)
|
||||
print("\n" + Style.BRIGHT + color + f"[{name}]" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + color + f"[{name}]" + Style.RESET_ALL)
|
||||
if cmd is not None:
|
||||
print(f" cwd: {cwd or REPO}")
|
||||
print(f" $ {shell_join(cmd)}")
|
||||
@@ -134,7 +136,7 @@ def exception_message(exc: BaseException) -> str:
|
||||
def print_failure(name: str, exc: BaseException | str) -> None:
|
||||
message = exc if isinstance(exc, str) else exception_message(exc)
|
||||
print(
|
||||
"\n" + Style.BRIGHT + Fore.RED + f"[{name} FAILED]" + Style.RESET_ALL,
|
||||
Style.BRIGHT + Fore.RED + f"[{name} FAILED]" + Style.RESET_ALL,
|
||||
file=sys.stderr,
|
||||
)
|
||||
for line in message.splitlines()[:20]:
|
||||
@@ -239,6 +241,17 @@ def reference_inputs_exist(
|
||||
)
|
||||
|
||||
|
||||
def reference_batch_dirs(root: Path, batch_size: int) -> list[Path]:
|
||||
return [root / f"batch_{index:06d}" for index in range(batch_size)]
|
||||
|
||||
|
||||
def reference_batch_outputs_exist(
|
||||
outputs_desc: list[tuple[int, str, int, list[int]]],
|
||||
reference_dirs: list[Path],
|
||||
) -> bool:
|
||||
return all(reference_outputs_exist(outputs_desc, reference_dir) for reference_dir in reference_dirs)
|
||||
|
||||
|
||||
def compare_simulator_outputs(
|
||||
output_bin: Path,
|
||||
outputs_desc: list[tuple[int, str, int, list[int]]],
|
||||
@@ -308,7 +321,7 @@ def select_pimsim_config(args: argparse.Namespace, hardware: dict[str, int]) ->
|
||||
if args.pimsim_mode == "latency" or config["sim_config"]["sim_time"] == args.pimsim_time_ms:
|
||||
return path
|
||||
raise ValueError(
|
||||
f"No pre-generated {args.pimsim_mode} pimsim-nn config for {args.pimsim_time_ms} ms matches {hardware}"
|
||||
f"No pre-generated {args.pimsim_mode} Pimsim config for {args.pimsim_time_ms} ms matches {hardware}"
|
||||
)
|
||||
|
||||
|
||||
@@ -329,13 +342,14 @@ def compile_reference(
|
||||
runner_dir = work_dir / "runner"
|
||||
build_dir = runner_dir / "build"
|
||||
raptor_dir.mkdir(parents=True, exist_ok=True)
|
||||
shutil.rmtree(build_dir, ignore_errors=True)
|
||||
build_dir.mkdir(parents=True, exist_ok=True)
|
||||
stem = model_path.stem
|
||||
onnx_ir_base = raptor_dir / stem
|
||||
runner_base = runner_dir / stem
|
||||
|
||||
run_logged(
|
||||
"Compile Reference ONNX IR",
|
||||
"Compile reference ONNX IR",
|
||||
[str(args.raptor_path), str(model_path), "-o", str(onnx_ir_base), "--EmitONNXIR",
|
||||
"--mlir-elide-elementsattrs-if-larger=16", "--enable-conv-opt-pass=false"],
|
||||
cwd=REPO,
|
||||
@@ -344,7 +358,7 @@ def compile_reference(
|
||||
stage="Compile ONNX",
|
||||
)
|
||||
run_logged(
|
||||
"Compile Reference Native",
|
||||
"Compile reference native",
|
||||
[str(args.raptor_path), "-O3", str(model_path), "-o", str(runner_base)],
|
||||
cwd=REPO,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
@@ -353,7 +367,7 @@ def compile_reference(
|
||||
)
|
||||
network_so = runner_base.with_suffix(".so")
|
||||
|
||||
print_step("Generate Runner Source", stage="Build Runner")
|
||||
print_step("Generate runner source", stage="Build runner")
|
||||
gen_network_runner(
|
||||
model_path,
|
||||
network_so,
|
||||
@@ -364,15 +378,15 @@ def compile_reference(
|
||||
)
|
||||
|
||||
run_logged(
|
||||
"Configure Runner",
|
||||
"Configure runner",
|
||||
["cmake", str(runner_dir), "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_C_FLAGS_RELEASE=-O3"],
|
||||
cwd=build_dir,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
steps=steps,
|
||||
stage="Build Runner",
|
||||
stage="Build runner",
|
||||
)
|
||||
run_logged(
|
||||
"Build Runner",
|
||||
"Build runner",
|
||||
["cmake", "--build", ".", "-j"],
|
||||
cwd=build_dir,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
@@ -382,6 +396,20 @@ def compile_reference(
|
||||
return build_dir / "runner"
|
||||
|
||||
|
||||
def ensure_reference_runner(
|
||||
args: argparse.Namespace,
|
||||
model_path: Path,
|
||||
work_dir: Path,
|
||||
steps: list[StepRecord],
|
||||
) -> Path:
|
||||
runner_dir = work_dir / "runner"
|
||||
runner_path = runner_dir / "build/runner"
|
||||
library_path = runner_dir / f"{model_path.stem}.so"
|
||||
if runner_uses_library(runner_path, library_path):
|
||||
return runner_path
|
||||
return compile_reference(args, model_path, work_dir, steps)
|
||||
|
||||
|
||||
def generate_reference_outputs(
|
||||
runner_path: Path,
|
||||
runner_build_dir: Path,
|
||||
@@ -390,6 +418,8 @@ def generate_reference_outputs(
|
||||
steps: list[StepRecord],
|
||||
args: argparse.Namespace,
|
||||
out_dir: Path,
|
||||
*,
|
||||
print_header: bool = True,
|
||||
) -> Path:
|
||||
inputs_dir = out_dir / "inputs"
|
||||
reference_dir = out_dir / "outputs"
|
||||
@@ -397,11 +427,12 @@ def generate_reference_outputs(
|
||||
reference_dir.mkdir(parents=True, exist_ok=True)
|
||||
flags, _ = save_inputs_to_files(model_path, arrays_in_order, inputs_dir)
|
||||
run_logged(
|
||||
"Run Reference",
|
||||
"Run reference",
|
||||
[str(runner_path), *flags, "--save-csv-dir", str(reference_dir)],
|
||||
cwd=runner_build_dir,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
steps=steps,
|
||||
print_header=print_header,
|
||||
)
|
||||
return reference_dir
|
||||
|
||||
@@ -414,19 +445,56 @@ def generate_reference_batch_outputs(
|
||||
steps: list[StepRecord],
|
||||
args: argparse.Namespace,
|
||||
out_dir: Path,
|
||||
*,
|
||||
print_header: bool = True,
|
||||
) -> list[Path]:
|
||||
return [
|
||||
generate_reference_outputs(
|
||||
if print_header:
|
||||
print_step("Run reference")
|
||||
references = []
|
||||
for index, sample in enumerate(input_batch):
|
||||
references.append(
|
||||
generate_reference_outputs(
|
||||
runner_path,
|
||||
runner_build_dir,
|
||||
model_path,
|
||||
sample,
|
||||
steps,
|
||||
args,
|
||||
out_dir / f"batch_{index:06d}",
|
||||
print_header=False,
|
||||
)
|
||||
)
|
||||
return references
|
||||
|
||||
|
||||
def prepare_reference_batch_outputs(
|
||||
runner_path: Path,
|
||||
runner_build_dir: Path,
|
||||
model_path: Path,
|
||||
input_batch: list[list[np.ndarray]],
|
||||
outputs_desc: list[tuple[int, str, int, list[int]]],
|
||||
steps: list[StepRecord],
|
||||
args: argparse.Namespace,
|
||||
out_dir: Path,
|
||||
*,
|
||||
print_header: bool = True,
|
||||
) -> list[Path]:
|
||||
reference_dirs = reference_batch_dirs(out_dir, len(input_batch))
|
||||
lock_path = out_dir.parent / ".reference.lock"
|
||||
with lock_path.open("w", encoding="utf-8") as lock:
|
||||
fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
|
||||
if reference_batch_outputs_exist(outputs_desc, reference_dirs):
|
||||
return reference_dirs
|
||||
return generate_reference_batch_outputs(
|
||||
runner_path,
|
||||
runner_build_dir,
|
||||
model_path,
|
||||
sample,
|
||||
input_batch,
|
||||
steps,
|
||||
args,
|
||||
out_dir / f"batch_{index:06d}",
|
||||
out_dir,
|
||||
print_header=print_header,
|
||||
)
|
||||
for index, sample in enumerate(input_batch)
|
||||
]
|
||||
|
||||
|
||||
def prepare_common_artifacts(
|
||||
@@ -437,10 +505,8 @@ def prepare_common_artifacts(
|
||||
inputs_desc, outputs_desc, arrays_in_order = load_model_inputs(model_path, args.seed)
|
||||
inputs_dir = common_dir / "inputs"
|
||||
outputs_dir = common_dir / "outputs"
|
||||
runner_path = common_dir / "runner/build/runner"
|
||||
steps: list[StepRecord] = []
|
||||
if not runner_path.exists():
|
||||
runner_path = compile_reference(args, model_path, common_dir, steps)
|
||||
runner_path = ensure_reference_runner(args, model_path, common_dir, steps)
|
||||
|
||||
inputs_ready = reference_inputs_exist(inputs_desc, inputs_dir)
|
||||
outputs_ready = reference_outputs_exist(outputs_desc, outputs_dir)
|
||||
@@ -478,7 +544,7 @@ def compile_raptor_target(
|
||||
"--pim-emit-json",
|
||||
*args.raptor_extra_arg,
|
||||
]
|
||||
print_step("Compile Raptor", cmd, REPO, "Compile PIM")
|
||||
print_step("Compile Raptor", cmd, REPO, "Compile Pim")
|
||||
start = time.perf_counter()
|
||||
command = shell_join(cmd)
|
||||
raptor_extra_args = [
|
||||
@@ -503,7 +569,7 @@ def compile_raptor_target(
|
||||
except Exception as exc:
|
||||
steps.append(
|
||||
StepRecord(
|
||||
name="Compile Raptor PIM",
|
||||
name="Compile Raptor Pim",
|
||||
duration_sec=time.perf_counter() - start,
|
||||
command=command,
|
||||
status="failed",
|
||||
@@ -514,7 +580,7 @@ def compile_raptor_target(
|
||||
|
||||
steps.append(
|
||||
StepRecord(
|
||||
name="Compile Raptor PIM",
|
||||
name="Compile Raptor Pim",
|
||||
duration_sec=time.perf_counter() - start,
|
||||
command=command,
|
||||
)
|
||||
@@ -548,7 +614,8 @@ def run_functional_validation(
|
||||
pim_dir,
|
||||
output_bin,
|
||||
dump_ranges,
|
||||
input_bins,
|
||||
input_bins[0].parent,
|
||||
batch_size,
|
||||
args.pimsim_mode,
|
||||
batch_output_dir,
|
||||
)
|
||||
@@ -559,7 +626,7 @@ def run_functional_validation(
|
||||
cwd=args.pim_simulator_dir,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
steps=steps,
|
||||
stage="Run Functional Simulation",
|
||||
stage="Run functional simulation",
|
||||
)
|
||||
max_diffs: dict[str, float] = {}
|
||||
failed_iterations = []
|
||||
@@ -610,9 +677,9 @@ def compile_pimcomp(
|
||||
(pimcomp_output_dir / name).unlink(missing_ok=True)
|
||||
model_name = args.pimcomp_model_name or f"compare_{model_path.stem}"
|
||||
frontend_json = frontend_json_dir / f"{model_name}.json"
|
||||
print_step("Compile PIMCOMP", stage="Compile PIM")
|
||||
# The original PIMCOMP frontend rewrites its input ONNX while loading it.
|
||||
# Isolate that mutation without changing the model sent through PIMCOMP.
|
||||
print_step("Compile Pimcomp", stage="Compile Pim")
|
||||
# The original Pimcomp frontend rewrites its input ONNX while loading it.
|
||||
# Isolate that mutation without changing the model sent through Pimcomp.
|
||||
with TemporaryDirectory(prefix="pimcomp-model-") as temp_dir:
|
||||
frontend_model = Path(temp_dir) / model_path.name
|
||||
shutil.copy2(model_path, frontend_model)
|
||||
@@ -625,12 +692,12 @@ def compile_pimcomp(
|
||||
str(frontend_json),
|
||||
]
|
||||
run_logged(
|
||||
"Compile PIMCOMP Frontend",
|
||||
"Compile Pimcomp frontend",
|
||||
frontend_cmd,
|
||||
cwd=args.pimcomp_dir / "frontend",
|
||||
timeout_sec=args.timeout_seconds,
|
||||
steps=steps,
|
||||
stage="Compile PIM",
|
||||
stage="Compile Pim",
|
||||
print_header=False,
|
||||
)
|
||||
backend_cmd = [
|
||||
@@ -643,12 +710,12 @@ def compile_pimcomp(
|
||||
]
|
||||
try:
|
||||
run_logged(
|
||||
"Compile PIMCOMP Backend",
|
||||
"Compile Pimcomp backend",
|
||||
backend_cmd,
|
||||
cwd=frontend_json_dir.parent,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
steps=steps,
|
||||
stage="Compile PIM",
|
||||
stage="Compile Pim",
|
||||
print_header=False,
|
||||
)
|
||||
finally:
|
||||
@@ -666,7 +733,7 @@ def export_pimcomp_for_pimsim_nn(simulation_info: Path, output_dir: Path) -> Pat
|
||||
sim_config = sim_info["config"]
|
||||
core_count = int(sim_config["core_cnt"])
|
||||
if core_count <= 0:
|
||||
raise ValueError("PIMCOMP SimulationInfo.gz must configure at least one core")
|
||||
raise ValueError("Pimcomp SimulationInfo.gz must configure at least one core")
|
||||
core_indices = range(core_count)
|
||||
|
||||
config = {
|
||||
@@ -709,7 +776,7 @@ def export_pimcomp_for_rust(
|
||||
output_dir: Path,
|
||||
) -> Path:
|
||||
if len(runtime_inputs) != 1:
|
||||
raise ValueError("PIMCOMP export currently requires exactly one runtime input tensor")
|
||||
raise ValueError("Pimcomp export currently requires exactly one runtime input tensor")
|
||||
if output_dir.exists():
|
||||
shutil.rmtree(output_dir)
|
||||
exporter = load_pimcomp_exporter()
|
||||
@@ -901,7 +968,7 @@ def export_pimcomp_for_rust(
|
||||
instructions.append(translated)
|
||||
out_offset += width
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported PIMCOMP op {op}")
|
||||
raise RuntimeError(f"Unsupported Pimcomp op {op}")
|
||||
|
||||
with open(output_dir / f"core_{core_idx}.json", "w", encoding="utf-8") as f:
|
||||
json.dump(instructions, f, separators=(",", ":"))
|
||||
@@ -935,7 +1002,7 @@ def run_pimsim_nn(
|
||||
cwd=args.pimsim_nn_build_dir,
|
||||
timeout_sec=args.timeout_seconds * 10.0,
|
||||
steps=steps,
|
||||
stage="Run Non-functional Simulation",
|
||||
stage="Run non-functional simulation",
|
||||
)
|
||||
return parse_pimsim_nn_metrics(output)
|
||||
|
||||
@@ -1091,12 +1158,9 @@ def restore_side_records(
|
||||
|
||||
|
||||
def default_common_dir(out_dir: Path) -> Path:
|
||||
if out_dir.name == "latency":
|
||||
return out_dir.parents[1] / "common"
|
||||
if out_dir.parent.name == "throughput":
|
||||
return out_dir.parents[2] / "common"
|
||||
if out_dir.name.startswith("arch-"):
|
||||
return out_dir.parent / "common"
|
||||
for parent in (out_dir, *out_dir.parents):
|
||||
if parent.name == "artifacts":
|
||||
return parent / "common"
|
||||
return out_dir / "common"
|
||||
|
||||
|
||||
@@ -1155,19 +1219,19 @@ def write_report(
|
||||
):
|
||||
report_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
lines = [
|
||||
"# Raptor vs PIMCOMP Comparison Report",
|
||||
"# Raptor vs Pimcomp comparison report",
|
||||
"",
|
||||
f"- Model: `{model_path}`",
|
||||
f"- Hardware: `{hardware.get('core_count', 'n/a')} cores`, `{hardware.get('crossbar_count', 'n/a')} xbars/core`, `{hardware.get('crossbar_size', 'n/a')}x{hardware.get('crossbar_size', 'n/a')}` crossbars, mesh `{hardware.get('mesh_rows', 'n/a')}x{hardware.get('mesh_cols', 'n/a')}`",
|
||||
f"- PIMCOMP pipeline: `{pimcomp_pipeline}`",
|
||||
f"- PIMCOMP replication: `{pimcomp_replication}`",
|
||||
f"- Pimcomp pipeline: `{pimcomp_pipeline}`",
|
||||
f"- Pimcomp replication: `{pimcomp_replication}`",
|
||||
"",
|
||||
]
|
||||
|
||||
if failures or any(step.status != "passed" for step in steps):
|
||||
lines.extend(
|
||||
[
|
||||
"## Failures / Skipped Work",
|
||||
"## Failures / skipped work",
|
||||
"",
|
||||
"The script did not abort. The failed stage was recorded and any dependent stage was skipped when its inputs were not available.",
|
||||
"",
|
||||
@@ -1182,21 +1246,21 @@ def write_report(
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"## Functional Validation",
|
||||
"## Functional validation",
|
||||
"",
|
||||
f"- Raptor via `pim-simulator`: `{validation_status(raptor_validation)}`",
|
||||
f"- PIMCOMP via exported `pim-simulator`: `{validation_status(pimcomp_validation)}`",
|
||||
f"- Pimcomp via exported `pim-simulator`: `{validation_status(pimcomp_validation)}`",
|
||||
]
|
||||
)
|
||||
if raptor_validation.error:
|
||||
lines.append(f"- Raptor validation note: `{raptor_validation.error.splitlines()[0]}`")
|
||||
if pimcomp_validation.error:
|
||||
lines.append(f"- PIMCOMP validation note: `{pimcomp_validation.error.splitlines()[0]}`")
|
||||
lines.append(f"- Pimcomp validation note: `{pimcomp_validation.error.splitlines()[0]}`")
|
||||
|
||||
lines.extend(["", "### Max Output Differences", ""])
|
||||
lines.extend(["", "### Max output differences", ""])
|
||||
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 |", "|---|---:|---:|"])
|
||||
for name in diff_names:
|
||||
lines.append(
|
||||
f"| `{name}` | {raptor_validation.max_diffs.get(name, float('nan')):.6e} | "
|
||||
@@ -1208,7 +1272,7 @@ def write_report(
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## pimsim-nn Performance",
|
||||
"## Pimsim performance",
|
||||
"",
|
||||
f"- Mode: `{pimsim_mode}`",
|
||||
"",
|
||||
@@ -1221,7 +1285,7 @@ def write_report(
|
||||
"|---|---|---:|---:|---:|---:|---:|",
|
||||
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"| PIMCOMP | {perf_status(pimcomp_perf)} | {perf_value(pimcomp_perf, 'average_latency_ms', 'ms')} | {perf_value(pimcomp_perf, 'throughput', 'samples/s')} | "
|
||||
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')} |",
|
||||
"",
|
||||
]
|
||||
@@ -1233,40 +1297,40 @@ def write_report(
|
||||
"|---|---|---:|---:|---:|",
|
||||
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"| PIMCOMP | {perf_status(pimcomp_perf)} | {perf_value(pimcomp_perf, 'latency_ms', 'ms')} | "
|
||||
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')} |",
|
||||
"",
|
||||
]
|
||||
)
|
||||
if raptor_perf.get("reason") or raptor_perf.get("error"):
|
||||
lines.append(f"- Raptor pimsim-nn note: `{(raptor_perf.get('reason') or raptor_perf.get('error')).splitlines()[0]}`")
|
||||
lines.append(f"- Raptor Pimsim note: `{(raptor_perf.get('reason') or raptor_perf.get('error')).splitlines()[0]}`")
|
||||
if pimcomp_perf.get("reason") or pimcomp_perf.get("error"):
|
||||
lines.append(f"- PIMCOMP pimsim-nn note: `{(pimcomp_perf.get('reason') or pimcomp_perf.get('error')).splitlines()[0]}`")
|
||||
lines.append(f"- Pimcomp Pimsim note: `{(pimcomp_perf.get('reason') or pimcomp_perf.get('error')).splitlines()[0]}`")
|
||||
if lines[-1] != "":
|
||||
lines.append("")
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"## Instruction Summary",
|
||||
"## Instruction summary",
|
||||
"",
|
||||
"| Compiler | Status | Active cores | Total instructions | Sends | Receives | MVMUL |",
|
||||
"|---|---|---:|---:|---:|---:|---:|",
|
||||
f"| Raptor | {'FAILED' if raptor_instr.get('error') else 'SKIPPED' if raptor_instr.get('skipped') else 'DONE'} | {raptor_instr.get('active_cores', 0)} | {raptor_instr.get('total_instructions', 0)} | {raptor_instr.get('op_counts', {}).get('send', 0)} | {raptor_instr.get('op_counts', {}).get('recv', 0)} | {raptor_instr.get('op_counts', {}).get('mvmul', 0)} |",
|
||||
f"| PIMCOMP | {'FAILED' if pimcomp_instr.get('error') else 'SKIPPED' if pimcomp_instr.get('skipped') else 'DONE'} | {pimcomp_instr.get('active_cores', 0)} | {pimcomp_instr.get('total_instructions', 0)} | {pimcomp_instr.get('op_counts', {}).get('send', 0)} | {pimcomp_instr.get('op_counts', {}).get('recv', 0)} | {pimcomp_instr.get('op_counts', {}).get('mvmul', 0)} |",
|
||||
f"| Pimcomp | {'FAILED' if pimcomp_instr.get('error') else 'SKIPPED' if pimcomp_instr.get('skipped') else 'DONE'} | {pimcomp_instr.get('active_cores', 0)} | {pimcomp_instr.get('total_instructions', 0)} | {pimcomp_instr.get('op_counts', {}).get('send', 0)} | {pimcomp_instr.get('op_counts', {}).get('recv', 0)} | {pimcomp_instr.get('op_counts', {}).get('mvmul', 0)} |",
|
||||
"",
|
||||
"### Raptor Op Distribution",
|
||||
"### Raptor op distribution",
|
||||
"",
|
||||
"| Op | Count | Share |",
|
||||
"|---|---:|---:|",
|
||||
*format_op_table(raptor_instr.get("op_counts", {}), raptor_instr.get("total_instructions", 0)),
|
||||
"",
|
||||
"### PIMCOMP Op Distribution",
|
||||
"### Pimcomp op distribution",
|
||||
"",
|
||||
"| Op | Count | Share |",
|
||||
"|---|---:|---:|",
|
||||
*format_op_table(pimcomp_instr.get("op_counts", {}), pimcomp_instr.get("total_instructions", 0)),
|
||||
"",
|
||||
"## Step Timings",
|
||||
"## Step timings",
|
||||
"",
|
||||
"| Step | Status | Duration (s) | Return code |",
|
||||
"|---|---|---:|---:|",
|
||||
@@ -1279,7 +1343,7 @@ def write_report(
|
||||
)
|
||||
failed_steps = [step for step in steps if step.status != "passed"]
|
||||
if failed_steps:
|
||||
lines.extend(["", "### Failed Step Details", ""])
|
||||
lines.extend(["", "### Failed step details", ""])
|
||||
for step in failed_steps:
|
||||
lines.extend(
|
||||
[
|
||||
@@ -1294,7 +1358,7 @@ def write_report(
|
||||
lines.append("")
|
||||
|
||||
if raptor_pass_timings:
|
||||
lines.extend(["", "## Raptor Pass Timings", "", "| Pass | Duration (s) |", "|---|---:|"])
|
||||
lines.extend(["", "## Raptor pass timings", "", "| Pass | Duration (s) |", "|---|---:|"])
|
||||
for name, duration in raptor_pass_timings.items():
|
||||
lines.append(f"| {name} | {duration:.4f} |")
|
||||
report_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
@@ -1325,7 +1389,7 @@ def main():
|
||||
parser.add_argument(
|
||||
"--pimcomp-config",
|
||||
type=Path,
|
||||
help="PIMCOMP hardware config (default: <pimcomp-dir>/config.json).",
|
||||
help="Pimcomp hardware config (default: <pimcomp-dir>/config.json).",
|
||||
)
|
||||
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)
|
||||
@@ -1347,7 +1411,7 @@ def main():
|
||||
parser.add_argument("--pimsim-mode", choices=["latency", "throughput"], default="latency")
|
||||
parser.add_argument("--batch-size", type=int, default=128)
|
||||
parser.add_argument("--pimcomp-pipeline", choices=["element", "batch"])
|
||||
parser.add_argument("--pimcomp-model-name", help="Use a PIMCOMP built-in model name such as vgg16.")
|
||||
parser.add_argument("--pimcomp-model-name", help="Use a Pimcomp built-in model name such as vgg16.")
|
||||
parser.add_argument(
|
||||
"--pimcomp-replication",
|
||||
choices=["balance", "W0H0", "uniform", "GA"],
|
||||
@@ -1361,27 +1425,41 @@ def main():
|
||||
parser.add_argument(
|
||||
"--reuse-pimcomp-dir",
|
||||
type=Path,
|
||||
help="Reuse a directory containing PIMCOMP SimulationInfo.gz, VerificationInfo.json, and MappingResult.txt.",
|
||||
help="Reuse a directory containing Pimcomp SimulationInfo.gz, VerificationInfo.json, and MappingResult.txt.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--reuse-pimcomp-report",
|
||||
type=Path,
|
||||
help="Preserve the PIMCOMP side of an existing comparison report without rerunning it.",
|
||||
help="Preserve the Pimcomp side of an existing comparison report without rerunning it.",
|
||||
)
|
||||
parser.add_argument("--skip-pimsim-nn", action="store_true")
|
||||
parser.add_argument(
|
||||
"--no-fast",
|
||||
action="store_true",
|
||||
help="Disable fast pimsim-nn throughput convergence for authoritative experiments.",
|
||||
help="Disable fast Pimsim throughput convergence for authoritative experiments.",
|
||||
)
|
||||
parser.add_argument("--verbose-raptor-compile", action="store_true")
|
||||
parser.add_argument("--raptor-extra-arg", action="append", default=[])
|
||||
parser.add_argument(
|
||||
"--raptor-only",
|
||||
action="store_true",
|
||||
help="Run Raptor compilation, validation, and simulation without running Pimcomp.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--fail-on-error",
|
||||
action="store_true",
|
||||
help="Return a non-zero status if a stage or semantic validation fails.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.raptor_only and any(
|
||||
option is not None
|
||||
for option in (
|
||||
args.reuse_raptor_report,
|
||||
args.reuse_pimcomp_dir,
|
||||
args.reuse_pimcomp_report,
|
||||
)
|
||||
):
|
||||
parser.error("--raptor-only cannot be combined with report reuse options")
|
||||
if args.reuse_pimcomp_dir is not None and args.reuse_pimcomp_report is not None:
|
||||
parser.error("--reuse-pimcomp-dir and --reuse-pimcomp-report are mutually exclusive")
|
||||
if args.pimsim_time_ms <= 0:
|
||||
@@ -1439,6 +1517,7 @@ def main():
|
||||
runner_path: Path | None = None
|
||||
reference_dir: Path | None = None
|
||||
reference_dirs: list[Path] = []
|
||||
reference_stage_started = False
|
||||
raptor_pim_dir: Path | None = None
|
||||
raptor_pimsim_dir: Path | None = None
|
||||
raptor_pass_timings: dict[str, float] = {}
|
||||
@@ -1451,11 +1530,19 @@ def main():
|
||||
reuse_raptor = args.reuse_raptor_report is not None
|
||||
|
||||
raptor_validation = skipped_validation("Raptor validation did not run")
|
||||
pimcomp_validation = failed_validation("PIMCOMP validation did not run")
|
||||
raptor_perf: dict[str, Any] = skipped_perf("pimsim-nn Raptor did not run")
|
||||
pimcomp_perf: dict[str, Any] = skipped_perf("pimsim-nn PIMCOMP did not run")
|
||||
pimcomp_validation = (
|
||||
skipped_validation("Skipped by --raptor-only")
|
||||
if args.raptor_only
|
||||
else failed_validation("Pimcomp validation did not run")
|
||||
)
|
||||
raptor_perf: dict[str, Any] = skipped_perf("Pimsim Raptor did not run")
|
||||
pimcomp_perf: dict[str, Any] = skipped_perf(
|
||||
"Skipped by --raptor-only" if args.raptor_only else "Pimsim Pimcomp did not run"
|
||||
)
|
||||
raptor_instr: dict[str, Any] = empty_instruction_summary("Raptor instruction parsing did not run")
|
||||
pimcomp_instr: dict[str, Any] = empty_instruction_summary("PIMCOMP instruction parsing did not run")
|
||||
pimcomp_instr: dict[str, Any] = empty_instruction_summary(
|
||||
"Skipped by --raptor-only" if args.raptor_only else "Pimcomp instruction parsing did not run"
|
||||
)
|
||||
|
||||
loaded_hardware = try_stage(failures, "Load hardware configuration", load_effective_hardware, args)
|
||||
if loaded_hardware is not None:
|
||||
@@ -1476,22 +1563,21 @@ def main():
|
||||
if args.reuse_pimcomp_dir is not None:
|
||||
reused_pimcomp_report_path = args.reuse_pimcomp_dir.resolve().parent / "comparison_report.json"
|
||||
reuse_pimcomp = reused_pimcomp_report_path is not None
|
||||
run_pimcomp = not args.raptor_only and not reuse_pimcomp
|
||||
if reuse_pimcomp:
|
||||
reused_pimcomp_report_path = reused_pimcomp_report_path.resolve()
|
||||
if not reused_pimcomp_report_path.exists():
|
||||
raise ValueError(f"Missing PIMCOMP report: {reused_pimcomp_report_path}")
|
||||
raise ValueError(f"Missing Pimcomp report: {reused_pimcomp_report_path}")
|
||||
with open(reused_pimcomp_report_path, "r", encoding="utf-8") as f:
|
||||
reused_pimcomp = json.load(f)
|
||||
if reused_pimcomp.get("pimcomp_model_source") != "original_onnx":
|
||||
raise ValueError("Reused PIMCOMP artifacts were not generated from the original ONNX model")
|
||||
raise ValueError("Reused Pimcomp artifacts were not generated from the original ONNX model")
|
||||
pimcomp_validation = CompareResult(**reused_pimcomp["pimcomp_validation"])
|
||||
pimcomp_perf = reused_pimcomp["pimcomp_performance"]
|
||||
pimcomp_instr = reused_pimcomp["pimcomp_instruction_summary"]
|
||||
simulation_info = path_from_report(reused_pimcomp, "pimcomp_simulation_info")
|
||||
pimcomp_export_dir = path_from_report(reused_pimcomp, "pimcomp_exported_pim")
|
||||
pimcomp_pimsim_dir = path_from_report(reused_pimcomp, "pimcomp_pimsim_nn")
|
||||
if pimcomp_pimsim_dir is None and (reused_pimcomp_report_path.parent / "pimsim_nn").is_dir():
|
||||
pimcomp_pimsim_dir = reused_pimcomp_report_path.parent / "pimsim_nn"
|
||||
pimsim_config = path_from_report(reused_pimcomp, "pimsim_config")
|
||||
restore_side_records(reused_pimcomp, "pimcomp", failures, steps)
|
||||
|
||||
@@ -1504,6 +1590,9 @@ def main():
|
||||
raise ValueError(f"Reused Raptor hardware differs: {reused_hardware} != {hardware}")
|
||||
reference_dir = common_dir / "outputs"
|
||||
runner_path = path_from_report(reused, "reference_runner")
|
||||
expected_library = common_dir / "runner" / f"{functional_model_path.stem}.so"
|
||||
if runner_path is None or not runner_uses_library(runner_path, expected_library):
|
||||
runner_path = ensure_reference_runner(args, functional_model_path, common_dir, steps)
|
||||
raptor_pim_dir = path_from_report(reused, "raptor_pim")
|
||||
raptor_pimsim_dir = path_from_report(reused, "raptor_pimsim_nn")
|
||||
arrays_in_order = load_saved_inputs(
|
||||
@@ -1519,7 +1608,6 @@ def main():
|
||||
print_step("Reuse Raptor")
|
||||
print(f" Report: {reuse_report_path}")
|
||||
|
||||
expected_runner_path = common_dir / "runner/build/runner"
|
||||
common_inputs_dir = common_dir / "inputs"
|
||||
common_reference_dir = common_dir / "outputs"
|
||||
|
||||
@@ -1539,20 +1627,15 @@ def main():
|
||||
else:
|
||||
inputs_ready = False
|
||||
|
||||
if expected_runner_path.exists():
|
||||
runner_path = expected_runner_path
|
||||
else:
|
||||
reference_compile = try_stage(
|
||||
failures,
|
||||
"Compile reference",
|
||||
compile_reference,
|
||||
args,
|
||||
functional_model_path,
|
||||
common_dir,
|
||||
steps,
|
||||
)
|
||||
if reference_compile is not None:
|
||||
runner_path = reference_compile
|
||||
runner_path = try_stage(
|
||||
failures,
|
||||
"Prepare reference runner",
|
||||
ensure_reference_runner,
|
||||
args,
|
||||
functional_model_path,
|
||||
common_dir,
|
||||
steps,
|
||||
)
|
||||
|
||||
if runner_path is not None and runner_path.exists() and model_io is not None:
|
||||
if inputs_ready and reference_outputs_exist(outputs_desc, common_reference_dir):
|
||||
@@ -1570,9 +1653,11 @@ def main():
|
||||
steps,
|
||||
args,
|
||||
common_dir,
|
||||
print_header=not reference_stage_started,
|
||||
)
|
||||
if generated_reference is not None:
|
||||
reference_dir = generated_reference
|
||||
reference_stage_started = True
|
||||
else:
|
||||
record_failure(
|
||||
failures,
|
||||
@@ -1589,28 +1674,32 @@ def main():
|
||||
write_input_batch_csv(out_dir / "inputs.csv", input_batch)
|
||||
raptor_input_bins = write_input_batch_binaries(input_batch, out_dir / "simulation/raptor_inputs")
|
||||
if args.pimsim_mode == "throughput":
|
||||
batch_reference_dir = common_dir / f"reference_batch_{args.batch_size}_seed_{args.seed}"
|
||||
throughput_references = try_stage(
|
||||
failures,
|
||||
"Run throughput references",
|
||||
generate_reference_batch_outputs,
|
||||
"Run reference",
|
||||
prepare_reference_batch_outputs,
|
||||
runner_path,
|
||||
runner_path.parent,
|
||||
functional_model_path,
|
||||
input_batch,
|
||||
outputs_desc,
|
||||
steps,
|
||||
args,
|
||||
out_dir / "reference",
|
||||
batch_reference_dir,
|
||||
print_header=not reference_stage_started,
|
||||
) if runner_path is not None and runner_path.exists() else None
|
||||
if throughput_references is not None:
|
||||
reference_dirs = throughput_references
|
||||
reference_dir = out_dir / "reference"
|
||||
reference_dir = batch_reference_dir
|
||||
reference_stage_started = True
|
||||
elif reference_dir is not None:
|
||||
reference_dirs = [reference_dir]
|
||||
|
||||
if not reuse_raptor and model_path.exists() and hardware["core_count"] > 0:
|
||||
compiled_raptor = try_stage(
|
||||
failures,
|
||||
"Compile Raptor PIM",
|
||||
"Compile Raptor Pim",
|
||||
compile_raptor_target,
|
||||
model_path,
|
||||
out_dir / "raptor",
|
||||
@@ -1623,8 +1712,8 @@ def main():
|
||||
elif not reuse_raptor:
|
||||
record_failure(
|
||||
failures,
|
||||
"Skip Raptor PIM compile",
|
||||
"Raptor PIM compile was skipped because the ONNX model or hardware configuration is not available.",
|
||||
"Skip Raptor Pim compile",
|
||||
"Raptor Pim compile was skipped because the ONNX model or hardware configuration is not available.",
|
||||
)
|
||||
|
||||
raptor_functional_pim_dir = raptor_pim_dir
|
||||
@@ -1635,7 +1724,7 @@ def main():
|
||||
):
|
||||
compiled_functional = try_stage(
|
||||
failures,
|
||||
"Compile Raptor functional PIM",
|
||||
"Compile Raptor functional Pim",
|
||||
compile_raptor_target,
|
||||
functional_model_path,
|
||||
out_dir / "raptor_functional",
|
||||
@@ -1659,9 +1748,9 @@ def main():
|
||||
if wrote_inputs and reference_dirs and outputs_desc:
|
||||
validation = try_stage(
|
||||
failures,
|
||||
"Functional Validation Raptor",
|
||||
"Functional validation Raptor",
|
||||
run_functional_validation,
|
||||
"Functional Validation Raptor",
|
||||
"Functional validation Raptor",
|
||||
raptor_functional_pim_dir,
|
||||
raptor_functional_pim_dir / "config.json",
|
||||
out_dir / "simulation/out.bin",
|
||||
@@ -1679,7 +1768,7 @@ def main():
|
||||
else:
|
||||
raptor_validation = skipped_validation("Raptor input materialization failed")
|
||||
elif not reuse_raptor:
|
||||
raptor_validation = skipped_validation("Raptor PIM compilation did not produce a PIM directory")
|
||||
raptor_validation = skipped_validation("Raptor Pim compilation did not produce a Pim directory")
|
||||
|
||||
pimcomp_model_path = model_path
|
||||
|
||||
@@ -1687,7 +1776,7 @@ def main():
|
||||
reused_pimcomp_dir = args.reuse_pimcomp_dir.resolve()
|
||||
copied_pimcomp = try_stage_success(
|
||||
failures,
|
||||
"Reuse PIMCOMP outputs",
|
||||
"Reuse Pimcomp outputs",
|
||||
copy_pimcomp_outputs,
|
||||
reused_pimcomp_dir,
|
||||
out_dir / "pimcomp/output",
|
||||
@@ -1695,12 +1784,12 @@ def main():
|
||||
if copied_pimcomp:
|
||||
verification_info = out_dir / "pimcomp/output/VerificationInfo.json"
|
||||
simulation_info = out_dir / "pimcomp/output/SimulationInfo.gz"
|
||||
print_step("Reuse PIMCOMP")
|
||||
print_step("Reuse Pimcomp")
|
||||
print(f" Directory: {reused_pimcomp_dir}")
|
||||
elif not reuse_pimcomp:
|
||||
elif run_pimcomp:
|
||||
compiled_pimcomp = try_stage(
|
||||
failures,
|
||||
"Compile PIMCOMP",
|
||||
"Compile Pimcomp",
|
||||
compile_pimcomp,
|
||||
args,
|
||||
pimcomp_model_path,
|
||||
@@ -1710,12 +1799,12 @@ def main():
|
||||
if compiled_pimcomp is not None:
|
||||
verification_info, simulation_info = compiled_pimcomp
|
||||
|
||||
if reuse_pimcomp:
|
||||
if reuse_pimcomp or args.raptor_only:
|
||||
pass
|
||||
elif verification_info is not None and simulation_info is not None and model_io is not None:
|
||||
exported = try_stage(
|
||||
failures,
|
||||
"Export PIMCOMP for Functional Validation",
|
||||
"Export Pimcomp for functional validation",
|
||||
export_pimcomp_for_rust,
|
||||
pimcomp_model_path,
|
||||
verification_info,
|
||||
@@ -1728,14 +1817,14 @@ def main():
|
||||
elif verification_info is None or simulation_info is None:
|
||||
record_failure(
|
||||
failures,
|
||||
"Export PIMCOMP for Functional Validation",
|
||||
"PIMCOMP functional export failed because PIMCOMP did not produce VerificationInfo.json and SimulationInfo.gz.",
|
||||
"Export Pimcomp for functional validation",
|
||||
"Pimcomp functional export failed because Pimcomp did not produce VerificationInfo.json and SimulationInfo.gz.",
|
||||
)
|
||||
else:
|
||||
record_failure(
|
||||
failures,
|
||||
"Export PIMCOMP for Functional Validation",
|
||||
"PIMCOMP functional export failed because model inputs are not available.",
|
||||
"Export Pimcomp for functional validation",
|
||||
"Pimcomp functional export failed because model inputs are not available.",
|
||||
)
|
||||
|
||||
if input_batch is not None and pimcomp_export_dir is not None:
|
||||
@@ -1745,12 +1834,12 @@ def main():
|
||||
transform=flatten_pimcomp_input,
|
||||
)
|
||||
|
||||
if not reuse_pimcomp and pimcomp_export_dir is not None and reference_dirs and outputs_desc:
|
||||
if run_pimcomp and pimcomp_export_dir is not None and reference_dirs and outputs_desc:
|
||||
validation = try_stage(
|
||||
failures,
|
||||
"Functional Validation PIMCOMP",
|
||||
"Functional validation Pimcomp",
|
||||
run_functional_validation,
|
||||
"Functional Validation PIMCOMP",
|
||||
"Functional validation Pimcomp",
|
||||
pimcomp_export_dir,
|
||||
pimcomp_export_dir / "config.json",
|
||||
out_dir / "simulation/pimcomp.out.bin",
|
||||
@@ -1761,11 +1850,11 @@ def main():
|
||||
args,
|
||||
channel_last=True,
|
||||
)
|
||||
pimcomp_validation = validation if validation is not None else failed_validation("PIMCOMP validation failed")
|
||||
elif reuse_pimcomp:
|
||||
pimcomp_validation = validation if validation is not None else failed_validation("Pimcomp validation failed")
|
||||
elif reuse_pimcomp or args.raptor_only:
|
||||
pass
|
||||
elif pimcomp_export_dir is None:
|
||||
pimcomp_validation = failed_validation("PIMCOMP functional export is not available")
|
||||
pimcomp_validation = failed_validation("Pimcomp functional export is not available")
|
||||
elif not reference_dirs:
|
||||
pimcomp_validation = failed_validation("Reference outputs are not available")
|
||||
else:
|
||||
@@ -1774,7 +1863,7 @@ def main():
|
||||
if not args.skip_pimsim_nn and hardware["core_count"] > 0:
|
||||
written_config = try_stage(
|
||||
failures,
|
||||
"Prepare pimsim-nn config",
|
||||
"Prepare Pimsim config",
|
||||
prepare_pimsim_config,
|
||||
args,
|
||||
hardware,
|
||||
@@ -1784,25 +1873,25 @@ def main():
|
||||
elif not args.skip_pimsim_nn:
|
||||
record_failure(
|
||||
failures,
|
||||
"Skip pimsim-nn config",
|
||||
"pimsim-nn config was skipped because the hardware configuration is not available.",
|
||||
"Skip Pimsim config",
|
||||
"Pimsim config was skipped because the hardware configuration is not available.",
|
||||
)
|
||||
|
||||
if args.skip_pimsim_nn:
|
||||
if not reuse_raptor:
|
||||
raptor_perf = skipped_perf("Skipped by --skip-pimsim-nn")
|
||||
if not reuse_pimcomp:
|
||||
if run_pimcomp:
|
||||
pimcomp_perf = skipped_perf("Skipped by --skip-pimsim-nn")
|
||||
elif pimsim_config is None:
|
||||
if not reuse_raptor:
|
||||
raptor_perf = skipped_perf("pimsim-nn config is not available")
|
||||
if not reuse_pimcomp:
|
||||
pimcomp_perf = skipped_perf("pimsim-nn config is not available")
|
||||
raptor_perf = skipped_perf("Pimsim config is not available")
|
||||
if run_pimcomp:
|
||||
pimcomp_perf = skipped_perf("Pimsim config is not available")
|
||||
else:
|
||||
if not reuse_raptor and raptor_pim_dir is not None:
|
||||
raptor_pimsim_dir = try_stage(
|
||||
failures,
|
||||
"Export Raptor for pimsim-nn",
|
||||
"Export Raptor for Pimsim",
|
||||
export_raptor_pimsim_artifact,
|
||||
raptor_pim_dir,
|
||||
out_dir / "raptor/pimsim_nn",
|
||||
@@ -1810,25 +1899,25 @@ def main():
|
||||
if raptor_pimsim_dir is not None:
|
||||
perf = try_stage(
|
||||
failures,
|
||||
"Non-Functional Simulation Raptor",
|
||||
"Non-functional simulation Raptor",
|
||||
run_pimsim_nn,
|
||||
"Non-Functional Simulation Raptor",
|
||||
"Non-functional simulation Raptor",
|
||||
raptor_pimsim_dir,
|
||||
pimsim_config,
|
||||
steps,
|
||||
args,
|
||||
)
|
||||
raptor_perf = perf if perf is not None else failed_perf("pimsim-nn Raptor failed")
|
||||
raptor_perf = perf if perf is not None else failed_perf("Pimsim Raptor failed")
|
||||
else:
|
||||
raptor_perf = failed_perf("Raptor pimsim-nn export failed")
|
||||
raptor_perf = failed_perf("Raptor Pimsim export failed")
|
||||
elif not reuse_raptor:
|
||||
raptor_perf = skipped_perf("Raptor PIM directory is not available")
|
||||
raptor_perf = skipped_perf("Raptor Pim directory is not available")
|
||||
|
||||
if not reuse_pimcomp:
|
||||
if run_pimcomp:
|
||||
if simulation_info is not None:
|
||||
pimcomp_pimsim_dir = try_stage(
|
||||
failures,
|
||||
"Export PIMCOMP for pimsim-nn",
|
||||
"Export Pimcomp for Pimsim",
|
||||
export_pimcomp_for_pimsim_nn,
|
||||
simulation_info,
|
||||
out_dir / "pimcomp/pimsim_nn",
|
||||
@@ -1836,32 +1925,32 @@ def main():
|
||||
if pimcomp_pimsim_dir is not None:
|
||||
perf = try_stage(
|
||||
failures,
|
||||
"Non-Functional Simulation PIMCOMP",
|
||||
"Non-functional simulation Pimcomp",
|
||||
run_pimsim_nn,
|
||||
"Non-Functional Simulation PIMCOMP",
|
||||
"Non-functional simulation Pimcomp",
|
||||
pimcomp_pimsim_dir,
|
||||
pimsim_config,
|
||||
steps,
|
||||
args,
|
||||
)
|
||||
pimcomp_perf = perf if perf is not None else failed_perf("pimsim-nn PIMCOMP failed")
|
||||
pimcomp_perf = perf if perf is not None else failed_perf("Pimsim Pimcomp failed")
|
||||
else:
|
||||
pimcomp_perf = failed_perf("PIMCOMP pimsim-nn export failed")
|
||||
pimcomp_perf = failed_perf("Pimcomp Pimsim export failed")
|
||||
else:
|
||||
pimcomp_perf = skipped_perf("PIMCOMP SimulationInfo.gz is not available")
|
||||
pimcomp_perf = skipped_perf("Pimcomp SimulationInfo.gz is not available")
|
||||
|
||||
if not reuse_raptor and raptor_pim_dir is not None and raptor_pim_dir.exists():
|
||||
parsed = try_stage(failures, "Parse Raptor instructions", parse_raptor_instructions, raptor_pim_dir)
|
||||
raptor_instr = parsed if parsed is not None else empty_instruction_summary(error="Failed to parse Raptor instructions")
|
||||
elif not reuse_raptor:
|
||||
raptor_instr = empty_instruction_summary("Raptor PIM directory is not available")
|
||||
raptor_instr = empty_instruction_summary("Raptor Pim directory is not available")
|
||||
|
||||
if not reuse_pimcomp:
|
||||
if run_pimcomp:
|
||||
if simulation_info is not None and simulation_info.exists():
|
||||
parsed = try_stage(failures, "Parse PIMCOMP instructions", parse_pimcomp_instructions, simulation_info)
|
||||
pimcomp_instr = parsed if parsed is not None else empty_instruction_summary(error="Failed to parse PIMCOMP instructions")
|
||||
parsed = try_stage(failures, "Parse Pimcomp instructions", parse_pimcomp_instructions, simulation_info)
|
||||
pimcomp_instr = parsed if parsed is not None else empty_instruction_summary(error="Failed to parse Pimcomp instructions")
|
||||
else:
|
||||
pimcomp_instr = empty_instruction_summary("PIMCOMP SimulationInfo.gz is not available")
|
||||
pimcomp_instr = empty_instruction_summary("Pimcomp SimulationInfo.gz is not available")
|
||||
|
||||
report_path = out_dir / "pimcomp/comparison_report.md"
|
||||
write_report(
|
||||
@@ -1890,7 +1979,7 @@ def main():
|
||||
"pimsim_time_ms": args.pimsim_time_ms,
|
||||
"pimcomp_pipeline": args.pimcomp_pipeline,
|
||||
"pimcomp_replication": args.pimcomp_replication,
|
||||
"pimcomp_model_source": "original_onnx",
|
||||
"pimcomp_model_source": "not_run" if args.raptor_only else "original_onnx",
|
||||
"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,
|
||||
@@ -1926,7 +2015,11 @@ def main():
|
||||
json.dump(json_report, f, indent=2)
|
||||
f.write("\n")
|
||||
|
||||
partial_side = "pimcomp" if reuse_raptor else "raptor" if args.reuse_pimcomp_report else None
|
||||
partial_side = (
|
||||
"pimcomp" if reuse_raptor
|
||||
else "raptor" if args.raptor_only or args.reuse_pimcomp_report
|
||||
else None
|
||||
)
|
||||
other_side = "RAPTOR" if partial_side == "pimcomp" else "PIMCOMP"
|
||||
relevant_failures = (
|
||||
failures if partial_side is None
|
||||
@@ -1947,7 +2040,7 @@ def main():
|
||||
for result in relevant_validations
|
||||
)
|
||||
failed = bool(relevant_failures or failed_steps or functional_failure)
|
||||
print("\n" + Style.BRIGHT + Fore.GREEN + "[Completed]" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + Fore.GREEN + "[Completed]" + Style.RESET_ALL)
|
||||
print(f" Report: {report_path}")
|
||||
print(f" JSON: {json_path}")
|
||||
if failures or failed_steps:
|
||||
+192
-105
@@ -19,33 +19,30 @@ from colorama import Fore, Style
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parents[5]
|
||||
SUITE = REPO / "validation/networks/pimcomp_models"
|
||||
sys.path.insert(0, str(REPO / "validation"))
|
||||
|
||||
from raptor_validation.pimsim_nn import parse_pimsim_nn_metrics # noqa: E402
|
||||
from raptor_validation.validate_one import STAGE_COLORS # noqa: E402
|
||||
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.validate_one import STAGE_COLORS
|
||||
|
||||
PIMCOMP_SOURCE = REPO / "third_party/PIMCOMP-NN"
|
||||
PIMCOMP_CONFIGS = REPO / "validation/pimsim_configs/pimcomp"
|
||||
COMPARE = Path(__file__).resolve().with_name("compare_raptor_pimcomp.py")
|
||||
COMPARE = Path(__file__).resolve().with_name("compare_raptor_pimcomp_model.py")
|
||||
ARCHES = tuple(sorted(path.name for path in PIMCOMP_CONFIGS.iterdir() if path.is_dir()))
|
||||
MODELS = {
|
||||
"vgg8": SUITE / "vgg8/vgg8-mnist-reconstructed.onnx",
|
||||
"resnet18": SUITE / "resnet18/resnet18-v1-7.onnx",
|
||||
"resnet34": SUITE / "resnet34/resnet34-v1-7.onnx",
|
||||
"googlenet": SUITE / "googlenet/googlenet-12-pimsim-nn.onnx",
|
||||
"yolo11n": SUITE / "yolo11n/yolo11n-pimsim-nn.onnx",
|
||||
}
|
||||
FUNCTIONAL_MODELS = {
|
||||
**MODELS,
|
||||
"yolo11n": REPO / "validation/networks/yolo11n/depth_51/yolo11n_depth_51.onnx",
|
||||
}
|
||||
DEFAULT_ARCHES = ("arch-a", "arch-b")
|
||||
COMPARISONS = (
|
||||
("latency", 1, "element"),
|
||||
("throughput", 2, "batch"),
|
||||
("throughput", 4, "batch"),
|
||||
("throughput", 8, "batch"),
|
||||
)
|
||||
RESULTS_FILENAME = "results_comparison.csv"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -67,39 +64,60 @@ def model_dir(root: Path | None, name: str) -> Path:
|
||||
return root / name if root is not None else MODELS[name].parent
|
||||
|
||||
|
||||
def result_dir(root: Path | None, name: str, arch: str, mode: str, pipeline: int) -> Path:
|
||||
base = model_dir(root, name)
|
||||
def result_dir(
|
||||
root: Path | None,
|
||||
name: str,
|
||||
arch: str,
|
||||
mode: str,
|
||||
pipeline: int,
|
||||
ablation_variant: str | None = None,
|
||||
) -> Path:
|
||||
base = artifacts_dir(model_dir(root, name))
|
||||
suffix = "latency" if mode == "latency" else f"throughput/pipeline{pipeline}"
|
||||
return base / arch / suffix
|
||||
result = base / arch / suffix
|
||||
if ablation_variant is not None:
|
||||
result /= Path("ablation") / ablation_variant
|
||||
return result
|
||||
|
||||
|
||||
def common_dir(root: Path | None, name: str) -> Path:
|
||||
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"
|
||||
return model_dir(root, name) / suffix
|
||||
base = common_root / name if common_root is not None else model_dir(root, name)
|
||||
return artifacts_dir(base) / suffix
|
||||
|
||||
|
||||
def clean_artifacts(root: Path | None, models: list[str], arches: list[str]) -> int:
|
||||
def clean_artifacts(
|
||||
root: Path | None,
|
||||
models: list[str],
|
||||
common_root: Path | None = None,
|
||||
) -> int:
|
||||
removed = 0
|
||||
for name in models:
|
||||
base = model_dir(root, name)
|
||||
arch_dirs = {base / arch for arch in arches}
|
||||
arch_dirs.update(path for path in base.glob("arch-*") if path.is_dir() and not path.is_symlink())
|
||||
for path in arch_dirs:
|
||||
for path in (artifacts_dir(base),):
|
||||
if path.is_dir() and not path.is_symlink():
|
||||
shutil.rmtree(path)
|
||||
removed += 1
|
||||
for common in (base / "common", base / "common-functional"):
|
||||
if common.is_dir() and not common.is_symlink():
|
||||
shutil.rmtree(common)
|
||||
removed += 1
|
||||
for path in (
|
||||
(root or SUITE) / "results.csv",
|
||||
(root or SUITE) / "results_latency.csv",
|
||||
(root or SUITE) / "results_throughput.csv",
|
||||
(root or SUITE) / RESULTS_FILENAME,
|
||||
(root or SUITE) / "results_ablation.csv",
|
||||
):
|
||||
if path.is_file() or path.is_symlink():
|
||||
path.unlink()
|
||||
removed += 1
|
||||
summary_root = root or SUITE
|
||||
if (summary_root / "ablation").is_dir():
|
||||
for path in (summary_root / "ablation").glob("*/results_comparison.csv"):
|
||||
path.unlink(missing_ok=True)
|
||||
removed += 1
|
||||
removed += remove_lock_files(summary_root)
|
||||
if common_root is not None:
|
||||
for name in models:
|
||||
path = artifacts_dir(common_root / name)
|
||||
if path.is_dir() and not path.is_symlink():
|
||||
shutil.rmtree(path)
|
||||
removed += 1
|
||||
removed += remove_lock_files(common_root)
|
||||
return removed
|
||||
|
||||
|
||||
@@ -108,8 +126,11 @@ def write_results_csv(
|
||||
arch: str,
|
||||
models: list[str],
|
||||
comparisons: tuple[tuple[str, int, str], ...] = COMPARISONS,
|
||||
ablation_variant: str | None = None,
|
||||
) -> Path:
|
||||
output = (root or SUITE) / "results.csv"
|
||||
output = (root or SUITE) / RESULTS_FILENAME
|
||||
if ablation_variant is not None:
|
||||
output = output.parent / "ablation" / ablation_variant / RESULTS_FILENAME
|
||||
fields = (
|
||||
"arch",
|
||||
"model",
|
||||
@@ -152,7 +173,9 @@ def write_results_csv(
|
||||
rows.append({field: row.get(field, "NA") for field in fields})
|
||||
for name in models:
|
||||
for comparison_mode, pipeline, pimcomp_pipeline in comparisons:
|
||||
report_path = result_dir(root, name, arch, comparison_mode, pipeline) / "pimcomp/comparison_report.json"
|
||||
report_path = result_dir(
|
||||
root, name, arch, comparison_mode, pipeline, ablation_variant
|
||||
) / "pimcomp/comparison_report.json"
|
||||
row = {
|
||||
"model": name,
|
||||
"arch": arch,
|
||||
@@ -215,6 +238,7 @@ def write_results_csv(
|
||||
int(row["raptor_pipeline"]),
|
||||
)
|
||||
)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fields, lineterminator="\n")
|
||||
writer.writeheader()
|
||||
@@ -275,11 +299,11 @@ def format_value(value: float | None) -> str:
|
||||
|
||||
|
||||
def print_stage(title: str, color: str) -> None:
|
||||
print("\n" + Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL, flush=True)
|
||||
print(Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL, flush=True)
|
||||
|
||||
|
||||
def print_completed(label: str, output: str = "") -> None:
|
||||
print("\n" + Style.BRIGHT + Fore.CYAN + f"[Completed {label}]" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + Fore.CYAN + f"[Completed {label}]" + Style.RESET_ALL)
|
||||
if output:
|
||||
print(output, end="" if output.endswith("\n") else "\n")
|
||||
print("=" * 72, flush=True)
|
||||
@@ -321,7 +345,7 @@ def validate_pimcomp_source() -> None:
|
||||
source = header.read_text(encoding="utf-8")
|
||||
for setting in ("int population_num = 200;", "int max_iteration = 1000;"):
|
||||
if setting not in source:
|
||||
raise RuntimeError(f"PIMCOMP paper setting is missing: {setting}")
|
||||
raise RuntimeError(f"Pimcomp paper setting is missing: {setting}")
|
||||
|
||||
|
||||
def comparison_command(
|
||||
@@ -337,6 +361,8 @@ def comparison_command(
|
||||
batch_size: int,
|
||||
timeout: float,
|
||||
fast: bool,
|
||||
raptor_extra_args: list[str] | tuple[str, ...] = (),
|
||||
raptor_only: bool = False,
|
||||
reuse_raptor_report: Path | None = None,
|
||||
reuse_pimcomp_dir: Path | None = None,
|
||||
reuse_pimcomp_report: Path | None = None,
|
||||
@@ -373,7 +399,9 @@ def comparison_command(
|
||||
pimcomp_pipeline,
|
||||
"--pimcomp-replication",
|
||||
"GA",
|
||||
*(["--raptor-only"] if raptor_only else []),
|
||||
f"--raptor-extra-arg=--pipeline={pipeline}",
|
||||
*[f"--raptor-extra-arg={arg}" for arg in raptor_extra_args],
|
||||
"--timeout-seconds",
|
||||
str(timeout),
|
||||
"--fail-on-error",
|
||||
@@ -418,13 +446,19 @@ def comparison_command_for(
|
||||
args.batch_size,
|
||||
args.timeout_seconds,
|
||||
not args.no_fast,
|
||||
args.raptor_extra_args,
|
||||
args.raptor_only,
|
||||
reuse_raptor_report=(
|
||||
report
|
||||
if args.only == "pimcomp"
|
||||
else None
|
||||
),
|
||||
reuse_pimcomp_dir=spec.shared_pimcomp_dir if reuse_shared_pimcomp and args.only != "raptor" else None,
|
||||
reuse_pimcomp_report=report if args.only == "raptor" else None,
|
||||
reuse_pimcomp_dir=(
|
||||
spec.shared_pimcomp_dir
|
||||
if reuse_shared_pimcomp and args.only != "raptor" and not args.raptor_only
|
||||
else None
|
||||
),
|
||||
reuse_pimcomp_report=report if args.only == "raptor" and not args.raptor_only else None,
|
||||
)
|
||||
|
||||
|
||||
@@ -497,20 +531,29 @@ def config_path(arch: str, mode: str, sim_time_ms: int, *, write: bool) -> Path:
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Compare supported PIMCOMP models with Raptor latency and throughput schedules."
|
||||
description="Compare supported Pimcomp models with Raptor latency and throughput schedules."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out-dir",
|
||||
type=Path,
|
||||
help="Result root (default: artifacts beside each model under validation/).",
|
||||
help="Suite root; generated artifacts go below each model's artifacts/ directory (default: validation/networks/pimcomp_models).",
|
||||
)
|
||||
parser.add_argument("--models", nargs="+", choices=MODELS, default=list(MODELS))
|
||||
parser.add_argument(
|
||||
"--arch",
|
||||
"--common-dir",
|
||||
type=Path,
|
||||
help="Shared root for per-model reference artifacts. Default: inside --out-dir.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ablation-variant",
|
||||
help="Place Raptor artifacts below <mode>/ablation/<variant> and write the comparison summary below ablation/<variant>.",
|
||||
)
|
||||
add_models_argument(parser)
|
||||
parser.add_argument(
|
||||
"--archs",
|
||||
nargs="+",
|
||||
choices=ARCHES,
|
||||
default=list(ARCHES),
|
||||
help="PIM architectures to run (default: all architectures).",
|
||||
default=list(DEFAULT_ARCHES),
|
||||
help=f"Pim architectures to run (default: {', '.join(DEFAULT_ARCHES)}).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
@@ -524,6 +567,11 @@ def main() -> int:
|
||||
choices=("raptor", "pimcomp"),
|
||||
help="Re-run only this compiler's compile, validation, and simulation stages; preserve the other side from its report.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--raptor-only",
|
||||
action="store_true",
|
||||
help="Run only Raptor; do not compile, validate, or simulate Pimcomp. Default: off.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pipeline",
|
||||
type=int,
|
||||
@@ -534,7 +582,7 @@ def main() -> int:
|
||||
"--pimsim-time-ms",
|
||||
type=int,
|
||||
default=1000,
|
||||
help="throughput pimsim-nn convergence deadline in ms (default: 1000).",
|
||||
help="Throughput Pimsim convergence deadline in ms (default: 1000).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--batch-size",
|
||||
@@ -564,15 +612,32 @@ def main() -> int:
|
||||
parser.add_argument(
|
||||
"--no-fast",
|
||||
action="store_true",
|
||||
help="Disable fast pimsim-nn throughput convergence for authoritative experiments.",
|
||||
help="Disable fast Pimsim throughput convergence for authoritative experiments.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--raptor-extra-arg",
|
||||
action="append",
|
||||
default=[],
|
||||
dest="raptor_extra_args",
|
||||
help="Additional argument to pass to Raptor; repeat as needed.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.ablation_variant and Path(args.ablation_variant).name != args.ablation_variant:
|
||||
parser.error("--ablation-variant must be a single directory name")
|
||||
|
||||
if args.only is not None and args.raptor_only:
|
||||
parser.error("--only cannot be combined with --raptor-only")
|
||||
|
||||
out_dir = args.out_dir.resolve() if args.out_dir is not None else None
|
||||
args.arch = list(dict.fromkeys(args.arch))
|
||||
common_root = args.common_dir.resolve() if args.common_dir is not None else None
|
||||
args.archs = list(dict.fromkeys(args.archs))
|
||||
args.mode = list(dict.fromkeys(args.mode))
|
||||
if args.clean:
|
||||
print(f"Removed {clean_artifacts(out_dir, args.models, args.arch)} comparison artifact path(s).")
|
||||
print(
|
||||
f"Removed {clean_artifacts(out_dir, args.models, common_root)} "
|
||||
"comparison artifact path(s)."
|
||||
)
|
||||
return 0
|
||||
|
||||
if args.jobs < 1:
|
||||
@@ -585,7 +650,7 @@ def main() -> int:
|
||||
parser.error("--timeout-seconds must be non-negative")
|
||||
comparisons_by_arch: dict[str, tuple[tuple[str, int, str], ...]] = {}
|
||||
configs_by_arch: dict[str, dict[str, Path]] = {}
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
comparisons = tuple(
|
||||
comparison for comparison in COMPARISONS
|
||||
if comparison[0] in args.mode
|
||||
@@ -611,10 +676,12 @@ def main() -> int:
|
||||
|
||||
if args.only is not None:
|
||||
missing_reuse = []
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
for name in args.models:
|
||||
for mode, pipeline, _ in comparisons_by_arch[arch]:
|
||||
comparison_dir = result_dir(out_dir, name, arch, mode, pipeline)
|
||||
comparison_dir = result_dir(
|
||||
out_dir, name, arch, mode, pipeline, args.ablation_variant
|
||||
)
|
||||
required = comparison_dir / "pimcomp/comparison_report.json"
|
||||
if not required.exists():
|
||||
missing_reuse.append(str(required))
|
||||
@@ -629,14 +696,15 @@ def main() -> int:
|
||||
+ ", ".join(missing_reuse)
|
||||
)
|
||||
|
||||
validate_pimcomp_source()
|
||||
if not args.raptor_only:
|
||||
validate_pimcomp_source()
|
||||
if out_dir is not None and not args.dry_run:
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print(Style.BRIGHT + f"Found {len(args.models)} PIMCOMP model(s) to compare." + Style.RESET_ALL)
|
||||
print(f"Architectures: {', '.join(args.arch)}")
|
||||
print(Style.BRIGHT + f"Found {len(args.models)} Pimcomp model(s) to compare." + Style.RESET_ALL)
|
||||
print(f"Architectures: {', '.join(args.archs)}")
|
||||
print(f"Modes: {', '.join(args.mode)}")
|
||||
print(f"Throughput pimsim time: {args.pimsim_time_ms} ms")
|
||||
print(f"Throughput Pimsim time: {args.pimsim_time_ms} ms")
|
||||
print(f"Max parallel jobs: {args.jobs}")
|
||||
print(
|
||||
f"Comparison jobs: "
|
||||
@@ -645,29 +713,36 @@ def main() -> int:
|
||||
print(f"Results root: {out_dir or SUITE}")
|
||||
print("=" * 72)
|
||||
|
||||
print_stage("Prepare shared artifacts", STAGE_COLORS["Build Runner"])
|
||||
for name in args.models:
|
||||
try:
|
||||
run(
|
||||
prepare_common_command(
|
||||
FUNCTIONAL_MODELS[name],
|
||||
common_dir(out_dir, name),
|
||||
args.timeout_seconds,
|
||||
),
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
finally:
|
||||
print_completed(f"shared artifacts: {name}")
|
||||
print_stage("Prepare shared artifacts", STAGE_COLORS["Build runner"])
|
||||
try:
|
||||
for name in args.models:
|
||||
try:
|
||||
run(
|
||||
prepare_common_command(
|
||||
FUNCTIONAL_MODELS[name],
|
||||
common_dir(out_dir, name, common_root),
|
||||
args.timeout_seconds,
|
||||
),
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
finally:
|
||||
print_completed(f"shared artifacts: {name}")
|
||||
except KeyboardInterrupt:
|
||||
remove_lock_files(out_dir or SUITE)
|
||||
print("Interrupted; cleaned validation lock files.", file=sys.stderr)
|
||||
return 130
|
||||
|
||||
failed = []
|
||||
comparison_specs: list[ComparisonSpec] = []
|
||||
shared_pimcomp_by_group: dict[tuple[str, str, str], Path] = {}
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
comparisons = comparisons_by_arch[arch]
|
||||
configs = configs_by_arch[arch]
|
||||
for name in args.models:
|
||||
for mode, pipeline, pimcomp_pipeline in comparisons:
|
||||
model_result_dir = result_dir(out_dir, name, arch, mode, pipeline)
|
||||
model_result_dir = result_dir(
|
||||
out_dir, name, arch, mode, pipeline, args.ablation_variant
|
||||
)
|
||||
label = f"{arch}/{name}/{mode}/pipeline{pipeline}"
|
||||
group = (arch, name, mode)
|
||||
shared_pimcomp_dir = shared_pimcomp_by_group.get(group)
|
||||
@@ -681,7 +756,7 @@ def main() -> int:
|
||||
model=MODELS[name],
|
||||
functional_model=FUNCTIONAL_MODELS[name],
|
||||
output_dir=model_result_dir,
|
||||
common_dir=common_dir(out_dir, name),
|
||||
common_dir=common_dir(out_dir, name, common_root),
|
||||
config=configs[mode],
|
||||
mode=mode,
|
||||
pipeline=pipeline,
|
||||
@@ -701,7 +776,8 @@ def main() -> int:
|
||||
if run(command, dry_run=True, check=False):
|
||||
failed.append(spec.label)
|
||||
elif comparison_specs:
|
||||
anchor_specs = comparison_specs if args.only == "raptor" else [
|
||||
raptor_only_run = args.only == "raptor" or args.raptor_only
|
||||
anchor_specs = comparison_specs if raptor_only_run else [
|
||||
spec for spec in comparison_specs if spec.anchor
|
||||
]
|
||||
anchor_jobs = [
|
||||
@@ -712,64 +788,75 @@ def main() -> int:
|
||||
)
|
||||
for spec in anchor_specs
|
||||
]
|
||||
dependent_specs = [] if args.only == "raptor" else [
|
||||
dependent_specs = [] if raptor_only_run else [
|
||||
spec for spec in comparison_specs if not spec.anchor
|
||||
]
|
||||
print_directly = min(args.jobs, len(comparison_specs)) == 1
|
||||
with (nullcontext(None) if print_directly else TemporaryDirectory(prefix="raptor-pimcomp-")) as log_dir:
|
||||
anchor_failed, log_offset = run_comparison_jobs(
|
||||
anchor_jobs,
|
||||
args.jobs,
|
||||
Path(log_dir) if log_dir else None,
|
||||
0,
|
||||
)
|
||||
failed.extend(anchor_failed)
|
||||
dependent_jobs = [
|
||||
(
|
||||
spec.label,
|
||||
comparison_command_for(
|
||||
spec,
|
||||
args,
|
||||
reuse_shared_pimcomp=pimcomp_artifact_ready(spec.shared_pimcomp_dir),
|
||||
),
|
||||
None,
|
||||
try:
|
||||
with (nullcontext(None) if print_directly else TemporaryDirectory(prefix="raptor-pimcomp-")) as log_dir:
|
||||
anchor_failed, log_offset = run_comparison_jobs(
|
||||
anchor_jobs,
|
||||
args.jobs,
|
||||
Path(log_dir) if log_dir else None,
|
||||
0,
|
||||
)
|
||||
for spec in dependent_specs
|
||||
]
|
||||
dependent_failed, _ = run_comparison_jobs(
|
||||
dependent_jobs,
|
||||
args.jobs,
|
||||
Path(log_dir) if log_dir else None,
|
||||
log_offset,
|
||||
)
|
||||
failed.extend(dependent_failed)
|
||||
failed.extend(anchor_failed)
|
||||
dependent_jobs = [
|
||||
(
|
||||
spec.label,
|
||||
comparison_command_for(
|
||||
spec,
|
||||
args,
|
||||
reuse_shared_pimcomp=pimcomp_artifact_ready(spec.shared_pimcomp_dir),
|
||||
),
|
||||
None,
|
||||
)
|
||||
for spec in dependent_specs
|
||||
]
|
||||
dependent_failed, _ = run_comparison_jobs(
|
||||
dependent_jobs,
|
||||
args.jobs,
|
||||
Path(log_dir) if log_dir else None,
|
||||
log_offset,
|
||||
)
|
||||
failed.extend(dependent_failed)
|
||||
except KeyboardInterrupt:
|
||||
remove_lock_files(out_dir or SUITE)
|
||||
print("Interrupted; cleaned validation lock files.", file=sys.stderr)
|
||||
return 130
|
||||
|
||||
if args.dry_run:
|
||||
return 1 if failed else 0
|
||||
|
||||
remove_lock_files(out_dir or SUITE)
|
||||
|
||||
results_path = None
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
results_path = write_results_csv(
|
||||
out_dir,
|
||||
arch,
|
||||
args.models,
|
||||
comparisons_by_arch[arch],
|
||||
args.ablation_variant,
|
||||
)
|
||||
assert results_path is not None
|
||||
print_stage(results_path.name, STAGE_COLORS["Compare Outputs"])
|
||||
print_stage(results_path.name, STAGE_COLORS["Compare outputs"])
|
||||
print(results_path.read_text(encoding="utf-8"), end="")
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
for name in args.models:
|
||||
for mode, pipeline, _ in comparisons_by_arch[arch]:
|
||||
label = f"{arch}/{name}/{mode}/pipeline{pipeline}"
|
||||
report_path = result_dir(out_dir, name, arch, mode, pipeline) / "pimcomp/comparison_report.json"
|
||||
report_path = result_dir(
|
||||
out_dir, name, arch, mode, pipeline, args.ablation_variant
|
||||
) / "pimcomp/comparison_report.json"
|
||||
compiler = "raptor" if args.raptor_only else args.only
|
||||
if not report_path.exists() or not comparison_passed(
|
||||
json.loads(report_path.read_text(encoding="utf-8")),
|
||||
args.only,
|
||||
compiler,
|
||||
):
|
||||
if label not in failed:
|
||||
failed.append(label)
|
||||
print("\n" + Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL)
|
||||
total_jobs = sum(len(args.models) * len(comparisons) for comparisons in comparisons_by_arch.values())
|
||||
print(Style.BRIGHT + f"Passed: {total_jobs - len(failed)}" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + f"Failed: {len(failed)}" + Style.RESET_ALL)
|
||||
@@ -1,6 +1,6 @@
|
||||
# PIMCOMP batch correctness reproduction
|
||||
# Pimcomp batch correctness reproduction
|
||||
|
||||
PIMCOMP's batch scheduler currently emits an incomplete standalone program for
|
||||
Pimcomp's batch scheduler currently emits an incomplete standalone program for
|
||||
models containing post operations. The generated `VerificationInfo.json` uses a
|
||||
negative `source_address` to identify the preceding node, but the batch
|
||||
verifier resolves that address by copying the provider tensor directly from
|
||||
@@ -17,8 +17,8 @@ the missing computation visible in Rust functional validation.
|
||||
For the checked-in GoogLeNet throughput/pipeline2 artifact, 39 provider tensors
|
||||
are referenced by batch loads. Nineteen have generated stores; twenty are
|
||||
never written. Preloading the provider tensors with the same ONNX Runtime
|
||||
intermediates used by PIMCOMP's verifier makes the exported program pass. This
|
||||
reproduces the verifier's input contract; it does not repair PIMCOMP's batch
|
||||
intermediates used by Pimcomp's verifier makes the exported program pass. This
|
||||
reproduces the verifier's input contract; it does not repair Pimcomp's batch
|
||||
schedule.
|
||||
|
||||
Run the default reproduction from the repository root:
|
||||
@@ -27,23 +27,59 @@ Run the default reproduction from the repository root:
|
||||
.venv/bin/python validation/tools/pim/pimcomp/correctness/run_prefill_experiment.py
|
||||
```
|
||||
|
||||
The launcher accepts an alternate comparison directory, model, and work
|
||||
work directory, and shared reference-artifact directory:
|
||||
The launcher accepts an alternate comparison directory, model, work directory,
|
||||
and shared reference-artifact directory:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/correctness/run_prefill_experiment.py \
|
||||
validation/networks/pimcomp_models/googlenet/arch-a/throughput/pipeline2 \
|
||||
validation/networks/pimcomp_models/googlenet/artifacts/arch-a/throughput/pipeline2 \
|
||||
validation/networks/pimcomp_models/googlenet/googlenet-12-pimsim-nn.onnx \
|
||||
/tmp/pimcomp-prefill-googlenet \
|
||||
validation/networks/pimcomp_models/googlenet/common
|
||||
validation/networks/pimcomp_models/googlenet/artifacts/arch-a/throughput/pipeline2/correctness/prefill \
|
||||
validation/networks/pimcomp_models/googlenet/artifacts/common
|
||||
```
|
||||
|
||||
Without the optional work-directory argument, the experiment uses the same
|
||||
`correctness/prefill/` directory below the comparison artifacts.
|
||||
|
||||
It runs the exported artifact once with its original memory image and once
|
||||
with [`prefill_batch_memory.py`](prefill_batch_memory.py), then compares both
|
||||
outputs with the recorded native reference. The expected GoogLeNet result is a
|
||||
baseline maximum difference near `6.70705` and a prefilled maximum difference
|
||||
near `4.05e-6`.
|
||||
|
||||
The issue is in PIMCOMP batch scheduling/validation semantics, not in the Rust
|
||||
The issue is in Pimcomp batch scheduling/validation semantics, not in the Rust
|
||||
simulator's vector-length interpretation. Vector lengths remain element counts
|
||||
as specified by the reference ISA.
|
||||
|
||||
## ResNet BatchNorm correctness gap
|
||||
|
||||
Pimcomp has a separate correctness limitation in its ResNet element pipeline.
|
||||
`BatchNormalization` is an ONNX operation. The frontend's
|
||||
[`fuse_operators()` pass](../../../../../third_party/PIMCOMP-NN/frontend/frontend.py#L535)
|
||||
marks it as fused and removes the node from the
|
||||
scheduled graph, but the released path does not fold the BatchNorm affine
|
||||
transform into the convolution weights and bias. The corresponding verifier
|
||||
workaround in [`verification.py`](../../../../../third_party/PIMCOMP-NN/verification/verification.py#L99)
|
||||
replaces BatchNorm parameters with identity values (scale and variance equal
|
||||
to one, bias and mean equal to zero) before running ONNX Runtime.
|
||||
|
||||
Therefore Pimcomp's native verifier and exported element program agree with
|
||||
each other, but they do not implement the original ResNet ONNX model. On the
|
||||
current ResNet-18 Arch-A latency artifact, using the same input:
|
||||
|
||||
| Comparison | Maximum absolute difference |
|
||||
|---|---:|
|
||||
| Pimcomp native verifier vs Rust export | `6.7e-8` |
|
||||
| Rust export vs original ONNX reference | `4.8294563` |
|
||||
|
||||
The Pimcomp output ranges from approximately `-0.187` to `0.220`, while the
|
||||
original ONNX output ranges from `-3.572` to `4.834`; 462 of 1000 final
|
||||
elements differ by more than one. This is not a Rust simulator or Python
|
||||
exporter regression. It is a Pimcomp model-semantics mismatch caused by
|
||||
dropping BatchNorm numerics. The same issue affects the ResNet-34 latency
|
||||
artifact. VGG and GoogLeNet do not show this particular mismatch because they
|
||||
do not contain the same ResNet BatchNorm path.
|
||||
|
||||
The latency comparison intentionally uses the original ONNX reference, so
|
||||
these Pimcomp rows must remain `FAIL` until Pimcomp folds BatchNorm correctly
|
||||
or the comparison explicitly uses a BatchNorm-neutralized reference.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Populate the host-side intermediate buffers expected by PIMCOMP batch mode."""
|
||||
"""Populate the host-side intermediate buffers expected by Pimcomp batch mode."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -23,7 +23,7 @@ def flatten_reference(value: np.ndarray) -> np.ndarray:
|
||||
elif value.ndim == 2:
|
||||
value = value.transpose()
|
||||
else:
|
||||
raise ValueError(f"PIMCOMP batch verification only flattens 2D/4D tensors, got {value.shape}")
|
||||
raise ValueError(f"Pimcomp batch verification only flattens 2D/4D tensors, got {value.shape}")
|
||||
return value.astype(np.float32, copy=False).reshape(-1)
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ def prefill_batch_memory(
|
||||
session = ort.InferenceSession(runtime_model.SerializeToString(), providers=["CPUExecutionProvider"])
|
||||
session_inputs = session.get_inputs()
|
||||
if len(session_inputs) != 1:
|
||||
raise ValueError("PIMCOMP export currently requires exactly one runtime input tensor")
|
||||
raise ValueError("Pimcomp export currently requires exactly one runtime input tensor")
|
||||
input_meta = session_inputs[0]
|
||||
input_tensor = np.loadtxt(input_path, delimiter=",", dtype=np.float32).reshape(input_meta.shape)
|
||||
provider_names = [node_list[index]["name"] for index in providers]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reproduce the PIMCOMP batch prefill correctness experiment."""
|
||||
"""Reproduce the Pimcomp batch prefill correctness experiment."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -7,7 +7,6 @@ import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
@@ -17,9 +16,9 @@ from prefill_batch_memory import prefill_batch_memory
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
REPO_ROOT = SCRIPT_DIR.parents[4]
|
||||
DEFAULT_COMPARISON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/arch-a/throughput/pipeline2"
|
||||
DEFAULT_COMPARISON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/artifacts/arch-a/throughput/pipeline2"
|
||||
DEFAULT_MODEL = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/googlenet-12-pimsim-nn.onnx"
|
||||
DEFAULT_COMMON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/common"
|
||||
DEFAULT_COMMON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/artifacts/common"
|
||||
SIMULATOR_MANIFEST = REPO_ROOT / "backend-simulators/pim/pim-simulator/Cargo.toml"
|
||||
|
||||
|
||||
@@ -28,6 +27,7 @@ def run_simulator(
|
||||
memory: Path,
|
||||
output: Path,
|
||||
dump: str,
|
||||
input_dir: Path,
|
||||
) -> None:
|
||||
subprocess.run(
|
||||
[
|
||||
@@ -44,6 +44,8 @@ def run_simulator(
|
||||
str(comparison_dir / "pimcomp/exported"),
|
||||
"--memory",
|
||||
str(memory),
|
||||
"--input-dir",
|
||||
str(input_dir),
|
||||
"-o",
|
||||
str(output),
|
||||
"-d",
|
||||
@@ -86,14 +88,18 @@ def main() -> int:
|
||||
model = args.model.resolve()
|
||||
common_dir = args.common_dir.resolve()
|
||||
if args.work_dir is None:
|
||||
work_dir = Path(tempfile.mkdtemp(prefix="pimcomp-prefill."))
|
||||
work_dir = comparison_dir / "correctness/prefill"
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
work_dir = args.work_dir.resolve()
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
input_path = common_dir / "inputs/in0.csv"
|
||||
if not input_path.is_file():
|
||||
input_path = comparison_dir / "inputs/in0.csv"
|
||||
simulator_input_dir = work_dir / "inputs"
|
||||
simulator_input_dir.mkdir(parents=True, exist_ok=True)
|
||||
np.loadtxt(input_path, delimiter=",", dtype=np.float32).tofile(
|
||||
simulator_input_dir / "input_0.bin"
|
||||
)
|
||||
|
||||
prefilled_memory = work_dir / "prefilled_memory.bin"
|
||||
metadata_path = work_dir / "metadata.json"
|
||||
@@ -117,8 +123,15 @@ def main() -> int:
|
||||
comparison_dir / "pimcomp/exported/memory.bin",
|
||||
baseline_output,
|
||||
dump,
|
||||
simulator_input_dir,
|
||||
)
|
||||
run_simulator(
|
||||
comparison_dir,
|
||||
prefilled_memory,
|
||||
prefilled_output,
|
||||
dump,
|
||||
simulator_input_dir,
|
||||
)
|
||||
run_simulator(comparison_dir, prefilled_memory, prefilled_output, dump)
|
||||
compare_outputs(baseline_output, prefilled_output, reference, work_dir)
|
||||
return 0
|
||||
|
||||
|
||||
@@ -98,9 +98,9 @@ def read_binary(path: Path) -> Program:
|
||||
magic, version, count = HEADER.unpack_from(data)
|
||||
expected_size = HEADER.size + count * RECORD.size
|
||||
if magic != b"PIMB":
|
||||
raise ValueError(f"{path}: invalid PIM binary magic")
|
||||
raise ValueError(f"{path}: invalid Pim binary magic")
|
||||
if version != 1:
|
||||
raise ValueError(f"{path}: unsupported PIM binary version {version}")
|
||||
raise ValueError(f"{path}: unsupported Pim binary version {version}")
|
||||
if len(data) != expected_size:
|
||||
raise ValueError(f"{path}: expected {expected_size} bytes, found {len(data)}")
|
||||
|
||||
@@ -399,7 +399,7 @@ def render_text(cores: list[int], transfers: list[Transfer], programs: dict[int,
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate SequenceDiagram.org text for PIM communication and intervening work."
|
||||
description="Generate SequenceDiagram.org text for Pim communication and intervening work."
|
||||
)
|
||||
parser.add_argument("pim_dir", type=Path, help="Directory containing core_<id>.json or core_<id>.pim files")
|
||||
selection = parser.add_mutually_exclusive_group(required=True)
|
||||
|
||||
@@ -22,6 +22,7 @@ if sys.version_info < (3, 10):
|
||||
)
|
||||
|
||||
from raptor_validation.onnx_utils import _ONNX_TO_NP, onnx_io, write_inputs_binary, write_inputs_to_memory_bin
|
||||
from raptor_validation.artifacts import artifacts_dir
|
||||
from raptor_validation.validate_one import (
|
||||
MODE_COMPILE_ONLY,
|
||||
build_dump_ranges,
|
||||
@@ -64,16 +65,17 @@ def find_network_onnx(network_dir: Path) -> Path:
|
||||
|
||||
|
||||
def local_case_paths(network_dir: Path, case_name: str):
|
||||
artifact_root = artifacts_dir(network_dir)
|
||||
return {
|
||||
"root": network_dir,
|
||||
"runner": network_dir / "runner" / "build" / "runner",
|
||||
"runner_build": network_dir / "runner" / "build",
|
||||
"raptor_pim": network_dir / "raptor" / "pim",
|
||||
"real_root": network_dir / "real_image_validation",
|
||||
"input_csv": network_dir / "real_image_validation" / "inputs" / f"{case_name}.csv",
|
||||
"ref_dir": network_dir / "real_image_validation" / "reference" / case_name,
|
||||
"sim_dir": network_dir / "real_image_validation" / "simulation" / case_name,
|
||||
"sim_bin": network_dir / "real_image_validation" / "simulation" / case_name / "out.bin",
|
||||
"root": artifact_root,
|
||||
"runner": artifact_root / "runner" / "build" / "runner",
|
||||
"runner_build": artifact_root / "runner" / "build",
|
||||
"raptor_pim": artifact_root / "raptor" / "pim",
|
||||
"real_root": artifact_root / "real_image_validation",
|
||||
"input_csv": artifact_root / "real_image_validation" / "inputs" / f"{case_name}.csv",
|
||||
"ref_dir": artifact_root / "real_image_validation" / "reference" / case_name,
|
||||
"sim_dir": artifact_root / "real_image_validation" / "simulation" / case_name,
|
||||
"sim_bin": artifact_root / "real_image_validation" / "simulation" / case_name / "out.bin",
|
||||
}
|
||||
|
||||
|
||||
@@ -102,10 +104,11 @@ def ensure_local_artifacts(args, network_onnx_path: Path):
|
||||
|
||||
|
||||
def ensure_existing_artifacts(network_dir: Path):
|
||||
artifact_root = artifacts_dir(network_dir)
|
||||
required_paths = [
|
||||
network_dir / "runner" / "build" / "runner",
|
||||
network_dir / "raptor" / "pim" / "config.json",
|
||||
network_dir / "raptor" / "pim" / "memory.bin",
|
||||
artifact_root / "runner" / "build" / "runner",
|
||||
artifact_root / "raptor" / "pim" / "config.json",
|
||||
artifact_root / "raptor" / "pim" / "memory.bin",
|
||||
]
|
||||
missing = [str(path) for path in required_paths if not path.exists()]
|
||||
if missing:
|
||||
@@ -137,7 +140,7 @@ def run_local_reference_and_simulator(args, network_dir: Path, network_onnx_path
|
||||
|
||||
tensor = np.loadtxt(paths["input_csv"], delimiter=",", dtype=np.float32).reshape(1, 3, 640, 640)
|
||||
write_inputs_to_memory_bin(paths["raptor_pim"] / "memory.bin", paths["raptor_pim"] / "config.json", [tensor])
|
||||
input_bin = paths["sim_dir"] / "input.bin"
|
||||
input_bin = paths["sim_dir"] / "input_0.bin"
|
||||
write_inputs_binary(input_bin, [tensor])
|
||||
|
||||
dump_ranges = build_dump_ranges(paths["raptor_pim"] / "config.json", output_descriptors)
|
||||
@@ -147,7 +150,8 @@ def run_local_reference_and_simulator(args, network_dir: Path, network_onnx_path
|
||||
paths["sim_bin"],
|
||||
dump_ranges,
|
||||
timeout_sec=args.command_timeout_seconds,
|
||||
input_paths=[input_bin],
|
||||
input_dir=input_bin.parent,
|
||||
batch_size=1,
|
||||
)
|
||||
return paths, output_descriptors[0]
|
||||
|
||||
@@ -225,7 +229,7 @@ def main():
|
||||
parser.add_argument(
|
||||
"--annotated-dir",
|
||||
type=Path,
|
||||
default=defaults["network_dir"] / "real_image_validation" / "annotated",
|
||||
default=defaults["network_dir"] / "artifacts" / "real_image_validation" / "annotated",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ def ensure_remote_artifacts(args):
|
||||
|
||||
def remote_case_paths(args, case_name: str):
|
||||
network_dir = Path(args.network_dir)
|
||||
root = Path(args.remote_project) / network_dir
|
||||
root = Path(args.remote_project) / network_dir / "artifacts"
|
||||
return {
|
||||
"root": root,
|
||||
"runner": root / "runner" / "build" / "runner",
|
||||
@@ -272,7 +272,7 @@ def remote_case_paths(args, case_name: str):
|
||||
"input_csv": root / "real_image_validation" / "inputs" / f"{case_name}.csv",
|
||||
"ref_dir": root / "real_image_validation" / "reference" / case_name,
|
||||
"sim_dir": root / "real_image_validation" / "simulation" / case_name,
|
||||
"sim_input": root / "real_image_validation" / "simulation" / case_name / "input.bin",
|
||||
"sim_input": root / "real_image_validation" / "simulation" / case_name / "input_0.bin",
|
||||
"sim_bin": root / "real_image_validation" / "simulation" / case_name / "out.bin",
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ PY
|
||||
f"cd {quoted_project}/backend-simulators/pim/pim-simulator && "
|
||||
f"cargo run --no-default-features --release --package pim-simulator --bin pim-simulator -- "
|
||||
f"-f {quoted_pim} -o {quoted_sim_bin} -d {dump_range} "
|
||||
f"--mode latency --input {shlex.quote(str(paths['sim_input']))}"
|
||||
f"--mode latency --batch-size 1 --input-dir {shlex.quote(str(paths['sim_input'].parent))}"
|
||||
)
|
||||
remote_bash(args.ssh_key, args.remote_host, sim_command)
|
||||
return paths
|
||||
@@ -408,7 +408,7 @@ def main():
|
||||
parser.add_argument("--core-count", type=int, default=144)
|
||||
parser.add_argument("--command-timeout-seconds", type=int, default=7200)
|
||||
parser.add_argument("--skip-compile", action="store_true")
|
||||
parser.add_argument("--annotated-dir", default="validation/networks/yolo11n/depth_51/real_image_validation/annotated")
|
||||
parser.add_argument("--annotated-dir", default="validation/networks/yolo11n/depth_51/artifacts/real_image_validation/annotated")
|
||||
args = parser.parse_args()
|
||||
|
||||
args.ssh_key = str(Path(args.ssh_key).expanduser())
|
||||
|
||||
Reference in New Issue
Block a user