simplify pimcomp compare workflow
Validate Operations / validate-operations (push) Has been cancelled

This commit is contained in:
NiccoloN
2026-07-28 15:11:56 +02:00
parent 78bfb8a9aa
commit 060a21172e
9 changed files with 231 additions and 149 deletions
+1
View File
@@ -6,6 +6,7 @@ operations/**/simulation
networks/**/inputs networks/**/inputs
networks/**/outputs networks/**/outputs
networks/**/raptor networks/**/raptor
networks/**/pimcomp
networks/**/runner networks/**/runner
networks/**/simulation networks/**/simulation
networks/**/real_image_val networks/**/real_image_val
+23 -1
View File
@@ -60,6 +60,27 @@ Validate a network or network slice:
`--operations-dir` may point to any directory tree containing `.onnx` files. `--operations-dir` may point to any directory tree containing `.onnx` files.
The script discovers them recursively. The script discovers them recursively.
## Raptor vs PIMCOMP comparison
The PIMCOMP paper-model suite has a one-command Arch-A comparison:
```bash
.venv/bin/python validation/tools/run_pimcomp_paper_latency.py
```
The runner verifies PIMCOMP's population-200, 1000-iteration GA settings,
builds Raptor and the existing `third_party/PIMCOMP-NN/build` tree, and compares
the four paper models one at a time. Each PIMCOMP GA run evaluates candidates
in parallel; set `OMP_NUM_THREADS` to control its worker count. Use
`--models vgg8` for one model or `--dry-run` to print the commands.
Generated artifacts are stored beside each model and ignored by Git. The
comparison reuses the same model-level `inputs/`, `outputs/`, `runner/`,
`raptor/`, and `simulation/` paths as regular validation. PIMCOMP-only
artifacts and `comparison_report.{md,json}` live under `pimcomp/`. See
[`networks/pimcomp_models/README.md`](networks/pimcomp_models/README.md) for
profiles, model provenance, limitations, and remote execution.
## Validation modes ## Validation modes
The default mode performs the complete workflow. The default mode performs the complete workflow.
@@ -167,7 +188,8 @@ Artifacts are written beside each model:
| `outputs/` | ONNX-MLIR reference output CSV files. | | `outputs/` | ONNX-MLIR reference output CSV files. |
| `raptor/` | Exported MLIR, dialect snapshots, reports, and final `pim/` artifacts. | | `raptor/` | Exported MLIR, dialect snapshots, reports, and final `pim/` artifacts. |
| `runner/` | Generated reference runner source, build tree, and shared library. | | `runner/` | Generated reference runner source, build tree, and shared library. |
| `simulation/out.bin` | Functional simulator output used for comparison. | | `simulation/` | Functional simulator outputs used for numerical comparison. |
| `pimcomp/` | PIMCOMP graph, instruction, simulator, and comparison-report artifacts. |
The `raptor/` directory may include `spatial0.mlir`, The `raptor/` directory may include `spatial0.mlir`,
`spatial1_graph.mlir`, `spatial2_trivial_merged.mlir`, `spatial1_graph.mlir`, `spatial2_trivial_merged.mlir`,
+30 -31
View File
@@ -136,12 +136,11 @@ random placement code occasionally segfaults; an unchanged retry succeeded in
the observed cases. the observed cases.
The paper's optimizer uses a genetic algorithm with population 200 and up to The paper's optimizer uses a genetic algorithm with population 200 and up to
1000 iterations. Select it with `-r=GA` for optimizer studies. The released 1000 iterations. The checked-out PIMCOMP submodule already has both paper
source keeps population 200 but sets `max_iteration = 3`, so reproducing the settings in `backend/GeneticAlgorithm.h`; select them with `-r=GA`. Fitness
paper's optimization search also requires changing that value in evaluation uses OpenMP and bounded bandwidth timelines. Set `OMP_NUM_THREADS`
`backend/GeneticAlgorithm.h`. GA allocates roughly 32 GB in its fast evaluator; to control its parallelism; otherwise OpenMP uses the available CPUs. The GA
use monolith below instead of reducing cores or crossbars when local RAM is uses the fixed seed `1`, so repeated serial and parallel runs are reproducible.
insufficient.
## Compare Raptor and PIMCOMP ## Compare Raptor and PIMCOMP
@@ -150,34 +149,37 @@ compiles both instruction streams, runs both through `pimsim-nn`, validates
Raptor through the Rust simulator, and writes Markdown and JSON reports. Raptor through the Rust simulator, and writes Markdown and JSON reports.
PIMCOMP Rust validation also runs when its optional exporter is available. PIMCOMP Rust validation also runs when its optional exporter is available.
To reproduce the complete Arch-A latency experiment, use the serial experiment To reproduce the complete Arch-A latency experiment, use the model-by-model
runner. It creates an isolated PIMCOMP build with population 200 and 1000 GA runner. It verifies the paper GA settings, builds Raptor and the existing
iterations, runs only the `element`/batch-1 latency pipeline, and invokes the `third_party/PIMCOMP-NN/build` tree, then runs the `element`/batch-1 comparison
comparison driver for one model at a time: for one model at a time:
```bash ```bash
.venv/bin/python validation/tools/run_pimcomp_paper_latency.py \ .venv/bin/python validation/tools/run_pimcomp_paper_latency.py
--out-dir /tmp/raptor-pimcomp-paper-latency
``` ```
Reports are written under `<out-dir>/<model>/comparison_report.{md,json}`. Each model directory reuses regular validation's ignored `inputs/`, `outputs/`,
Use `--models vgg8` to run one model, `--resume` after an interruption, or `runner/`, `raptor/`, and `simulation/` paths. PIMCOMP-only artifacts and
`--dry-run` to inspect every command. The runner continues after a failed model `comparison_report.{md,json}` live under `pimcomp/`. The frontend regenerates
so all reports are produced. one isolated `models/JSON/` graph because PIMCOMP requires that relative
layout; it is removed after a successful backend run and the shared submodule
model directory is never modified. Models requiring BatchNormalization folding
also receive a prepared ONNX file; other models use the original ONNX directly.
PIMCOMP's source tree and build directory remain unchanged at runtime. Use
`--models vgg8` to run one model, `--resume` after an interruption, `--dry-run`
to inspect every command, or `--out-dir PATH` to keep results outside
`validation/`. The runner continues after a failed model so all reports are
produced.
Arch-A low-latency example: Arch-A low-latency example:
```bash ```bash
RAPTOR_ROOT=$PWD RAPTOR_ROOT=$PWD
PIMCOMP="$RAPTOR_ROOT/third_party/PIMCOMP-NN"
CONFIG_BACKUP=$(mktemp)
cp "$PIMCOMP/config.json" "$CONFIG_BACKUP"
trap 'cp "$CONFIG_BACKUP" "$PIMCOMP/config.json"' EXIT
cp "$RAPTOR_ROOT/validation/pimsim_configs/pimcomp/arch-a/latency_config.json" "$PIMCOMP/config.json"
"$RAPTOR_ROOT/.venv/bin/python" "$RAPTOR_ROOT/validation/tools/compare_raptor_pimcomp.py" \ "$RAPTOR_ROOT/.venv/bin/python" "$RAPTOR_ROOT/validation/tools/compare_raptor_pimcomp.py" \
--model "$RAPTOR_ROOT/validation/networks/pimcomp_models/resnet34/resnet34-v1-7.onnx" \ --model "$RAPTOR_ROOT/validation/networks/pimcomp_models/resnet34/resnet34-v1-7.onnx" \
--out-dir /tmp/compare-resnet34-arch-a-ll \ --out-dir "$RAPTOR_ROOT/validation/networks/pimcomp_models/resnet34" \
--pimcomp-config "$RAPTOR_ROOT/validation/pimsim_configs/pimcomp/arch-a/latency_config.json" \
--core-count 168 \ --core-count 168 \
--crossbar-count 96 \ --crossbar-count 96 \
--crossbar-size 128 \ --crossbar-size 128 \
@@ -196,11 +198,9 @@ If only semantic and instruction comparison is required, add
`--skip-pimsim-nn`. A VGG-8 run with the same Arch-A LL settings passed both `--skip-pimsim-nn`. A VGG-8 run with the same Arch-A LL settings passed both
semantic validations with maximum output differences below `5e-10`. semantic validations with maximum output differences below `5e-10`.
The current PIMCOMP-NN submodule does not include the optional The comparison runner enables `--fail-on-error`, so a failed compiler,
`verification/export_to_pim_simulator.py` helper. The driver therefore records simulation, or semantic validation makes the command fail while preserving the
PIMCOMP Rust semantic validation as skipped, while PIMCOMP compilation, generated report.
instruction reporting, and `pimsim-nn` latency still run. Use
`--fail-on-error` only when that semantic export helper is available.
Current Raptor status: Current Raptor status:
@@ -247,14 +247,13 @@ python3 -m venv .venv
.venv/bin/python -m pip install numpy onnx onnxruntime onnxsim colorama .venv/bin/python -m pip install numpy onnx onnxruntime onnxsim colorama
# Run every latency comparison serially. # Run every latency comparison serially.
.venv/bin/python validation/tools/run_pimcomp_paper_latency.py \ .venv/bin/python validation/tools/run_pimcomp_paper_latency.py
--out-dir /tmp/raptor-pimcomp-paper-latency
``` ```
Copy reports back without transferring large compiler artifacts: Copy reports back without transferring large compiler artifacts:
```bash ```bash
rsync -az --include='*/' --include='comparison_report.*' --exclude='*' \ rsync -az --include='*/' --include='comparison_report.*' --exclude='*' \
monolith:/tmp/raptor-pimcomp-paper-latency/ \ "monolith:$REMOTE_REPO/validation/networks/pimcomp_models/" \
/tmp/raptor-pimcomp-paper-latency/ validation/networks/pimcomp_models/
``` ```
@@ -25,7 +25,6 @@ No throughput experiment was run.
```bash ```bash
.venv/bin/python validation/tools/run_pimcomp_paper_latency.py \ .venv/bin/python validation/tools/run_pimcomp_paper_latency.py \
--out-dir /tmp/raptor-pimcomp-paper-latency \
--models googlenet --models googlenet
``` ```
+5
View File
@@ -1,5 +1,6 @@
import re import re
import shlex import shlex
import shutil
import subprocess import subprocess
from pathlib import Path from pathlib import Path
from colorama import Fore, Style from colorama import Fore, Style
@@ -43,6 +44,10 @@ def _format_command(cmd):
def compile_with_raptor(network_path, raptor_onnx_path: Path, output_base: Path, def compile_with_raptor(network_path, raptor_onnx_path: Path, output_base: Path,
crossbar_size, crossbar_count, core_count, crossbar_size, crossbar_count, core_count,
raptor_extra_args, cwd, verbose, reporter, timeout_sec): raptor_extra_args, cwd, verbose, reporter, timeout_sec):
pim_dir = Path(output_base).parent / "pim"
if pim_dir.exists():
shutil.rmtree(pim_dir)
# Define the arguments, with the possibility to set crossbar size and count # Define the arguments, with the possibility to set crossbar size and count
args = [ args = [
network_path, network_path,
+11 -11
View File
@@ -23,6 +23,16 @@ STAGE_TITLES = (
"Compare Outputs", "Compare Outputs",
"Run Non-functional Simulation", "Run Non-functional Simulation",
) )
STAGE_COLORS = {
STAGE_TITLES[0]: Fore.BLUE,
STAGE_TITLES[1]: Fore.MAGENTA,
STAGE_TITLES[2]: Fore.YELLOW,
STAGE_TITLES[3]: Fore.GREEN,
STAGE_TITLES[4]: Fore.CYAN,
STAGE_TITLES[5]: Fore.MAGENTA,
STAGE_TITLES[6]: Fore.YELLOW,
STAGE_TITLES[7]: Fore.BLUE,
}
STAGE_COUNT = len(STAGE_TITLES) STAGE_COUNT = len(STAGE_TITLES)
GENERATED_DIR_NAMES = ("inputs", "outputs", "raptor", "runner", "simulation") GENERATED_DIR_NAMES = ("inputs", "outputs", "raptor", "runner", "simulation")
@@ -294,17 +304,7 @@ def clean_workspace_artifacts(workspace_dir, model_stem):
def print_stage(reporter, model_index, model_total, model_name, title): def print_stage(reporter, model_index, model_total, model_name, title):
stage_colors = { color = STAGE_COLORS.get(title, Fore.WHITE)
STAGE_TITLES[0]: Fore.BLUE,
STAGE_TITLES[1]: Fore.MAGENTA,
STAGE_TITLES[2]: Fore.YELLOW,
STAGE_TITLES[3]: Fore.GREEN,
STAGE_TITLES[4]: Fore.CYAN,
STAGE_TITLES[5]: Fore.MAGENTA,
STAGE_TITLES[6]: Fore.YELLOW,
STAGE_TITLES[7]: Fore.BLUE,
}
color = stage_colors.get(title, Fore.WHITE)
reporter.log(Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL) reporter.log(Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL)
reporter.set_stage(model_index, model_total, model_name, title) reporter.set_stage(model_index, model_total, model_name, title)
+114 -56
View File
@@ -21,6 +21,7 @@ from typing import Any
import numpy as np import numpy as np
import onnx import onnx
from colorama import Fore, Style
REPO = Path(__file__).resolve().parents[2] REPO = Path(__file__).resolve().parents[2]
@@ -37,7 +38,11 @@ from raptor_validation.onnx_utils import ( # noqa: E402
write_inputs_to_memory_bin, write_inputs_to_memory_bin,
) )
from raptor_validation.raptor import compile_with_raptor # noqa: E402 from raptor_validation.raptor import compile_with_raptor # noqa: E402
from raptor_validation.validate_one import build_dump_ranges, parse_pim_simulator_outputs # noqa: E402 from raptor_validation.validate_one import ( # noqa: E402
STAGE_COLORS,
build_dump_ranges,
parse_pim_simulator_outputs,
)
@dataclass @dataclass
@@ -73,8 +78,14 @@ def shell_join(cmd: list[str]) -> str:
return shlex.join(str(arg) for arg in cmd) return shlex.join(str(arg) for arg in cmd)
def print_step(name: str, cmd: list[str] | None = None, cwd: Path | None = None): def print_step(
print(f"\n[{name}]") name: str,
cmd: list[str] | None = None,
cwd: Path | None = None,
stage: str | None = None,
):
color = STAGE_COLORS.get(stage or name, Fore.WHITE)
print("\n" + Style.BRIGHT + color + f"[{name}]" + Style.RESET_ALL)
if cmd is not None: if cmd is not None:
print(f" cwd: {cwd or REPO}") print(f" cwd: {cwd or REPO}")
print(f" $ {shell_join(cmd)}") print(f" $ {shell_join(cmd)}")
@@ -108,9 +119,12 @@ def exception_message(exc: BaseException) -> str:
def print_failure(name: str, exc: BaseException | str) -> None: def print_failure(name: str, exc: BaseException | str) -> None:
message = exc if isinstance(exc, str) else exception_message(exc) message = exc if isinstance(exc, str) else exception_message(exc)
print(f"\n[{name} FAILED]") print(
"\n" + Style.BRIGHT + Fore.RED + f"[{name} FAILED]" + Style.RESET_ALL,
file=sys.stderr,
)
for line in message.splitlines()[:20]: for line in message.splitlines()[:20]:
print(f" {line}") print(Fore.RED + f" {line}" + Style.RESET_ALL, file=sys.stderr)
def run_logged( def run_logged(
@@ -120,8 +134,9 @@ def run_logged(
cwd: Path, cwd: Path,
timeout_sec: float, timeout_sec: float,
steps: list[StepRecord], steps: list[StepRecord],
stage: str | None = None,
) -> str: ) -> str:
print_step(name, cmd, cwd) print_step(name, cmd, cwd, stage)
start = time.perf_counter() start = time.perf_counter()
command = shell_join(cmd) command = shell_join(cmd)
try: try:
@@ -202,29 +217,29 @@ def load_saved_inputs(
def prepare_pimcomp_model(model_path: Path, out_dir: Path) -> Path: def prepare_pimcomp_model(model_path: Path, out_dir: Path) -> Path:
model = onnx.load(model_path)
if not any(node.op_type == "BatchNormalization" for node in model.graph.node):
return model_path
out_dir.mkdir(parents=True, exist_ok=True) out_dir.mkdir(parents=True, exist_ok=True)
output_path = out_dir / f"{model_path.stem}_pimcomp.onnx" output_path = out_dir / f"{model_path.stem}_pimcomp.onnx"
model = onnx.load(model_path) from onnxsim import simplify
model, equivalent = simplify(model, check_n=1)
if not equivalent:
raise RuntimeError("Conv+BatchNormalization folding changed the model output")
if any(node.op_type == "BatchNormalization" for node in model.graph.node): if any(node.op_type == "BatchNormalization" for node in model.graph.node):
from onnxsim import simplify import onnxruntime as ort
model, equivalent = simplify(model, check_n=1) options = ort.SessionOptions()
if not equivalent: options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_BASIC
raise RuntimeError("Conv+BatchNormalization folding changed the model output") options.optimized_model_filepath = str(output_path)
ort.InferenceSession(str(model_path), options, providers=["CPUExecutionProvider"])
model = onnx.load(output_path)
if any(node.op_type == "BatchNormalization" for node in model.graph.node): if any(node.op_type == "BatchNormalization" for node in model.graph.node):
import onnxruntime as ort raise RuntimeError("PIMCOMP model preparation did not eliminate BatchNormalization")
options = ort.SessionOptions()
options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_BASIC
options.optimized_model_filepath = str(output_path)
ort.InferenceSession(str(model_path), options, providers=["CPUExecutionProvider"])
model = onnx.load(output_path)
if any(node.op_type == "BatchNormalization" for node in model.graph.node):
raise RuntimeError("PIMCOMP model preparation did not eliminate BatchNormalization")
else:
onnx.save(model, output_path)
else: else:
shutil.copy2(model_path, output_path) onnx.save(model, output_path)
return output_path return output_path
@@ -259,8 +274,7 @@ def sanitize_output_name(name: str) -> str:
def load_effective_hardware(args: argparse.Namespace) -> dict[str, int]: def load_effective_hardware(args: argparse.Namespace) -> dict[str, int]:
config_path = args.pimcomp_dir / "config.json" with open(args.pimcomp_config, "r", encoding="utf-8") as f:
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f) config = json.load(f)
rows, cols = config["chip_config"]["network_config"]["layout"] rows, cols = config["chip_config"]["network_config"]["layout"]
xbar_h, xbar_w = config["chip_config"]["core_config"]["matrix_config"]["xbar_size"] xbar_h, xbar_w = config["chip_config"]["core_config"]["matrix_config"]["xbar_size"]
@@ -304,7 +318,7 @@ def compile_reference(
work_dir: Path, work_dir: Path,
steps: list[StepRecord], steps: list[StepRecord],
) -> Path: ) -> Path:
raptor_dir = work_dir / "reference" raptor_dir = work_dir / "raptor"
runner_dir = work_dir / "runner" runner_dir = work_dir / "runner"
build_dir = runner_dir / "build" build_dir = runner_dir / "build"
raptor_dir.mkdir(parents=True, exist_ok=True) raptor_dir.mkdir(parents=True, exist_ok=True)
@@ -320,6 +334,7 @@ def compile_reference(
cwd=REPO, cwd=REPO,
timeout_sec=args.timeout_seconds, timeout_sec=args.timeout_seconds,
steps=steps, steps=steps,
stage="Compile ONNX",
) )
run_logged( run_logged(
"Reference Native Compile", "Reference Native Compile",
@@ -327,10 +342,11 @@ def compile_reference(
cwd=REPO, cwd=REPO,
timeout_sec=args.timeout_seconds, timeout_sec=args.timeout_seconds,
steps=steps, steps=steps,
stage="Compile ONNX",
) )
network_so = runner_base.with_suffix(".so") network_so = runner_base.with_suffix(".so")
print_step("Generate Runner Source") print_step("Generate Runner Source", stage="Build Runner")
gen_network_runner( gen_network_runner(
model_path, model_path,
network_so, network_so,
@@ -346,6 +362,7 @@ def compile_reference(
cwd=build_dir, cwd=build_dir,
timeout_sec=args.timeout_seconds, timeout_sec=args.timeout_seconds,
steps=steps, steps=steps,
stage="Build Runner",
) )
run_logged( run_logged(
"Build Runner", "Build Runner",
@@ -367,7 +384,7 @@ def generate_reference_outputs(
out_dir: Path, out_dir: Path,
) -> Path: ) -> Path:
inputs_dir = out_dir / "inputs" inputs_dir = out_dir / "inputs"
reference_dir = out_dir / "reference_outputs" reference_dir = out_dir / "outputs"
inputs_dir.mkdir(parents=True, exist_ok=True) inputs_dir.mkdir(parents=True, exist_ok=True)
reference_dir.mkdir(parents=True, exist_ok=True) reference_dir.mkdir(parents=True, exist_ok=True)
flags, _ = save_inputs_to_files(model_path, arrays_in_order, inputs_dir) flags, _ = save_inputs_to_files(model_path, arrays_in_order, inputs_dir)
@@ -402,7 +419,7 @@ def compile_raptor_target(
"--pim-emit-json", "--pim-emit-json",
*args.raptor_extra_arg, *args.raptor_extra_arg,
] ]
print_step("Compile Raptor PIM", cmd, REPO) print_step("Compile Raptor PIM", cmd, REPO, "Compile PIM")
start = time.perf_counter() start = time.perf_counter()
command = shell_join(cmd) command = shell_join(cmd)
raptor_extra_args = ["--pim-emit-json", *args.raptor_extra_arg] raptor_extra_args = ["--pim-emit-json", *args.raptor_extra_arg]
@@ -446,6 +463,7 @@ def run_rust_validation(
label: str, label: str,
pim_dir: Path, pim_dir: Path,
config_path: Path, config_path: Path,
output_bin: Path,
outputs_desc: list[tuple[int, str, int, list[int]]], outputs_desc: list[tuple[int, str, int, list[int]]],
reference_dir: Path, reference_dir: Path,
steps: list[StepRecord], steps: list[StepRecord],
@@ -453,7 +471,6 @@ def run_rust_validation(
*, *,
channel_last: bool = False, channel_last: bool = False,
) -> CompareResult: ) -> CompareResult:
output_bin = pim_dir.parent / "semantic_validation" / "out.bin"
dump_ranges = build_dump_ranges(config_path, outputs_desc) dump_ranges = build_dump_ranges(config_path, outputs_desc)
cmd = [ cmd = [
"cargo", "cargo",
@@ -472,14 +489,14 @@ def run_rust_validation(
"-d", "-d",
dump_ranges, dump_ranges,
] ]
simulation_dir = pim_dir.parent / "semantic_validation" output_bin.parent.mkdir(parents=True, exist_ok=True)
simulation_dir.mkdir(parents=True, exist_ok=True)
run_logged( run_logged(
label, label,
cmd, cmd,
cwd=args.pim_simulator_dir, cwd=args.pim_simulator_dir,
timeout_sec=args.timeout_seconds, timeout_sec=args.timeout_seconds,
steps=steps, steps=steps,
stage="Run Functional Simulation",
) )
return compare_simulator_outputs( return compare_simulator_outputs(
output_bin, output_bin,
@@ -503,16 +520,20 @@ def compile_pimcomp(
out_dir: Path, out_dir: Path,
steps: list[StepRecord], steps: list[StepRecord],
) -> tuple[Path, Path]: ) -> tuple[Path, Path]:
out_dir.mkdir(parents=True, exist_ok=True) frontend_json_dir = out_dir / "models/JSON"
pimcomp_output_dir = args.pimcomp_dir / "output" frontend_json_dir.mkdir(parents=True, exist_ok=True)
runtime_config = out_dir / "config.json"
if args.pimcomp_config != runtime_config:
shutil.copy2(args.pimcomp_config, runtime_config)
pimcomp_output_dir = out_dir / "output"
pimcomp_output_dir.mkdir(parents=True, exist_ok=True) pimcomp_output_dir.mkdir(parents=True, exist_ok=True)
for name in ("SimulationInfo.gz", "VerificationInfo.json", "MappingResult.txt"): for name in ("SimulationInfo.gz", "VerificationInfo.json", "MappingResult.txt"):
(pimcomp_output_dir / name).unlink(missing_ok=True) (pimcomp_output_dir / name).unlink(missing_ok=True)
model_name = args.pimcomp_model_name or f"compare_{model_path.stem}" model_name = args.pimcomp_model_name or f"compare_{model_path.stem}"
frontend_json = args.pimcomp_dir / "models/JSON" / f"{model_name}.json" frontend_json = frontend_json_dir / f"{model_name}.json"
frontend_cmd = [ frontend_cmd = [
sys.executable, sys.executable,
"frontend.py", str(args.pimcomp_dir / "frontend/frontend.py"),
"--model_path", "--model_path",
str(model_path), str(model_path),
"--save_path", "--save_path",
@@ -524,6 +545,7 @@ def compile_pimcomp(
cwd=args.pimcomp_dir / "frontend", cwd=args.pimcomp_dir / "frontend",
timeout_sec=args.timeout_seconds, timeout_sec=args.timeout_seconds,
steps=steps, steps=steps,
stage="Compile PIM",
) )
backend_cmd = [ backend_cmd = [
str(args.pimcomp_dir / "build" / "PIMCOMP-NN"), str(args.pimcomp_dir / "build" / "PIMCOMP-NN"),
@@ -536,12 +558,13 @@ def compile_pimcomp(
run_logged( run_logged(
"PIMCOMP Backend", "PIMCOMP Backend",
backend_cmd, backend_cmd,
cwd=args.pimcomp_dir / "build", cwd=frontend_json_dir.parent,
timeout_sec=args.timeout_seconds, timeout_sec=args.timeout_seconds,
steps=steps, steps=steps,
stage="Compile PIM",
) )
copy_pimcomp_outputs(pimcomp_output_dir, out_dir) remove_tree(frontend_json_dir.parent)
return out_dir / "VerificationInfo.json", out_dir / "SimulationInfo.gz" return pimcomp_output_dir / "VerificationInfo.json", pimcomp_output_dir / "SimulationInfo.gz"
def export_pimcomp_for_pimsim_nn(simulation_info: Path, output_dir: Path) -> Path: def export_pimcomp_for_pimsim_nn(simulation_info: Path, output_dir: Path) -> Path:
@@ -836,6 +859,7 @@ def run_pimsim_nn(
cwd=args.pimsim_nn_build_dir, cwd=args.pimsim_nn_build_dir,
timeout_sec=args.timeout_seconds * 10.0, timeout_sec=args.timeout_seconds * 10.0,
steps=steps, steps=steps,
stage="Run Non-functional Simulation",
) )
return parse_pimsim_nn_report(output) return parse_pimsim_nn_report(output)
@@ -1170,6 +1194,11 @@ def main():
parser.add_argument("--raptor-path", default=REPO / "build_release/Release/bin/onnx-mlir", type=Path) parser.add_argument("--raptor-path", default=REPO / "build_release/Release/bin/onnx-mlir", type=Path)
parser.add_argument("--onnx-include-dir", default=REPO / "onnx-mlir/include", type=Path) parser.add_argument("--onnx-include-dir", default=REPO / "onnx-mlir/include", type=Path)
parser.add_argument("--pimcomp-dir", default=REPO / "third_party/PIMCOMP-NN", type=Path) parser.add_argument("--pimcomp-dir", default=REPO / "third_party/PIMCOMP-NN", type=Path)
parser.add_argument(
"--pimcomp-config",
type=Path,
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("--pim-simulator-dir", default=REPO / "backend-simulators/pim/pim-simulator", type=Path)
parser.add_argument("--pimsim-nn-build-dir", default=REPO / "backend-simulators/pim/pimsim-nn/build", type=Path) parser.add_argument("--pimsim-nn-build-dir", default=REPO / "backend-simulators/pim/pimsim-nn/build", type=Path)
parser.add_argument("--seed", type=int, default=0) parser.add_argument("--seed", type=int, default=0)
@@ -1213,6 +1242,12 @@ def main():
args.pimcomp_pipeline = "element" if args.pimsim_mode == "latency" else "batch" args.pimcomp_pipeline = "element" if args.pimsim_mode == "latency" else "batch"
model_path = args.model.resolve() model_path = args.model.resolve()
args.pimcomp_dir = args.pimcomp_dir.resolve()
args.pimcomp_config = (
args.pimcomp_config.resolve()
if args.pimcomp_config is not None
else args.pimcomp_dir / "config.json"
)
out_dir = args.out_dir.resolve() out_dir = args.out_dir.resolve()
out_dir.mkdir(parents=True, exist_ok=True) out_dir.mkdir(parents=True, exist_ok=True)
@@ -1265,14 +1300,19 @@ def main():
raise ValueError(f"Reused Raptor hardware differs: {reused_hardware} != {hardware}") raise ValueError(f"Reused Raptor hardware differs: {reused_hardware} != {hardware}")
reference_dir = Path(reused["paths"]["reference_outputs"]) reference_dir = Path(reused["paths"]["reference_outputs"])
raptor_pim_dir = Path(reused["paths"]["raptor_pim"]) raptor_pim_dir = Path(reused["paths"]["raptor_pim"])
arrays_in_order, runtime_inputs = load_saved_inputs(model_path, inputs_desc, reuse_report_path.parent / "inputs") arrays_in_order, runtime_inputs = load_saved_inputs(
model_path,
inputs_desc,
reference_dir.parent / "inputs",
)
raptor_validation = CompareResult(**reused["raptor_validation"]) raptor_validation = CompareResult(**reused["raptor_validation"])
raptor_perf = reused["raptor_performance"] raptor_perf = reused["raptor_performance"]
raptor_instr = reused["raptor_instruction_summary"] raptor_instr = reused["raptor_instruction_summary"]
raptor_pass_timings = reused["raptor_pass_timings"] raptor_pass_timings = reused["raptor_pass_timings"]
print(f"\n[Reuse Raptor]\n Report: {reuse_report_path}") print_step("Reuse Raptor")
print(f" Report: {reuse_report_path}")
expected_runner_path = out_dir / "runner" / "build" / "runner" expected_runner_path = out_dir / "runner/build/runner"
if not reuse_raptor: if not reuse_raptor:
reference_compile = try_stage( reference_compile = try_stage(
@@ -1289,7 +1329,14 @@ def main():
else: else:
if expected_runner_path.exists(): if expected_runner_path.exists():
runner_path = expected_runner_path runner_path = expected_runner_path
print(f"\n[Continue] Reusing partial runner: {runner_path}") print(
"\n"
+ Style.BRIGHT
+ Fore.YELLOW
+ "[Continue]"
+ Style.RESET_ALL
+ f" Reusing partial runner: {runner_path}"
)
if not reuse_raptor and runner_path is not None and runner_path.exists() and model_io is not None: if not reuse_raptor and runner_path is not None and runner_path.exists() and model_io is not None:
generated_reference = try_stage( generated_reference = try_stage(
@@ -1350,6 +1397,7 @@ def main():
"Rust Validation Raptor", "Rust Validation Raptor",
raptor_pim_dir, raptor_pim_dir,
raptor_pim_dir / "config.json", raptor_pim_dir / "config.json",
out_dir / "simulation/out.bin",
outputs_desc, outputs_desc,
reference_dir, reference_dir,
steps, steps,
@@ -1370,7 +1418,7 @@ def main():
"Prepare PIMCOMP model", "Prepare PIMCOMP model",
prepare_pimcomp_model, prepare_pimcomp_model,
model_path, model_path,
out_dir / "pimcomp_model", out_dir / "pimcomp/model",
) )
if args.reuse_pimcomp_dir is not None: if args.reuse_pimcomp_dir is not None:
@@ -1385,7 +1433,8 @@ def main():
if copied_pimcomp: if copied_pimcomp:
verification_info = out_dir / "pimcomp/VerificationInfo.json" verification_info = out_dir / "pimcomp/VerificationInfo.json"
simulation_info = out_dir / "pimcomp/SimulationInfo.gz" simulation_info = out_dir / "pimcomp/SimulationInfo.gz"
print(f"\n[Reuse PIMCOMP]\n Directory: {reused_pimcomp_dir}") print_step("Reuse PIMCOMP")
print(f" Directory: {reused_pimcomp_dir}")
else: else:
compiled_pimcomp = try_stage( compiled_pimcomp = try_stage(
failures, failures,
@@ -1408,7 +1457,7 @@ def main():
verification_info, verification_info,
simulation_info, simulation_info,
runtime_inputs, runtime_inputs,
out_dir / "pimcomp_exported", out_dir / "pimcomp/exported",
) )
if exported is not None: if exported is not None:
pimcomp_export_dir = exported pimcomp_export_dir = exported
@@ -1433,6 +1482,7 @@ def main():
"Rust Validation PIMCOMP", "Rust Validation PIMCOMP",
pimcomp_export_dir, pimcomp_export_dir,
pimcomp_export_dir / "config.json", pimcomp_export_dir / "config.json",
out_dir / "simulation/pimcomp.out.bin",
outputs_desc, outputs_desc,
reference_dir, reference_dir,
steps, steps,
@@ -1494,7 +1544,7 @@ def main():
"Export PIMCOMP for pimsim-nn", "Export PIMCOMP for pimsim-nn",
export_pimcomp_for_pimsim_nn, export_pimcomp_for_pimsim_nn,
simulation_info, simulation_info,
out_dir / "pimcomp_pimsim_nn", out_dir / "pimcomp/pimsim_nn",
) )
if pimcomp_pimsim_dir is not None: if pimcomp_pimsim_dir is not None:
perf = try_stage( perf = try_stage(
@@ -1525,7 +1575,7 @@ def main():
else: 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 / "comparison_report.md" report_path = out_dir / "pimcomp/comparison_report.md"
write_report( write_report(
report_path, report_path,
model_path=model_path, model_path=model_path,
@@ -1569,22 +1619,30 @@ def main():
"report_markdown": str(report_path), "report_markdown": str(report_path),
}, },
} }
json_path = out_dir / "comparison_report.json" json_path = out_dir / "pimcomp/comparison_report.json"
with open(json_path, "w", encoding="utf-8") as f: with open(json_path, "w", encoding="utf-8") as f:
json.dump(json_report, f, indent=2) json.dump(json_report, f, indent=2)
f.write("\n") f.write("\n")
print(f"\n[Done]") failed_steps = any(step.status != "passed" for step in steps)
print(f" Report: {report_path}")
print(f" JSON: {json_path}")
if failures or any(step.status != "passed" for step in steps):
print(f" Completed with {len(failures)} recorded failure/skipped stage(s).")
semantic_failure = any( semantic_failure = any(
result.status == "done" and not result.passed result.status == "done" and not result.passed
for result in (raptor_validation, pimcomp_validation) for result in (raptor_validation, pimcomp_validation)
) )
if args.fail_on_error and (failures or any(step.status != "passed" for step in steps) or semantic_failure): failed = bool(failures or failed_steps or semantic_failure)
result = "FAIL" if args.fail_on_error and failed else "DONE" if failed else "PASS"
color = Fore.RED if result == "FAIL" else Fore.YELLOW if result == "DONE" else Fore.GREEN
print("\n" + Style.BRIGHT + f"Result: {color}{result}" + Style.RESET_ALL)
print(f" Report: {report_path}")
print(f" JSON: {json_path}")
if failures or failed_steps:
print(
Fore.YELLOW
+ f" Completed with {len(failures)} recorded failure/skipped stage(s)."
+ Style.RESET_ALL
)
if args.fail_on_error and failed:
raise SystemExit(1) raise SystemExit(1)
+46 -48
View File
@@ -2,17 +2,18 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import re
import shlex import shlex
import shutil
import subprocess import subprocess
import sys import sys
from pathlib import Path from pathlib import Path
from colorama import Fore, Style
REPO = Path(__file__).resolve().parents[2] REPO = Path(__file__).resolve().parents[2]
SUITE = REPO / "validation/networks/pimcomp_models" SUITE = REPO / "validation/networks/pimcomp_models"
PIMCOMP_SOURCE = REPO / "third_party/PIMCOMP-NN" PIMCOMP_SOURCE = REPO / "third_party/PIMCOMP-NN"
PIMCOMP_CONFIG = REPO / "validation/pimsim_configs/pimcomp/arch-a/latency_config.json"
COMPARE = REPO / "validation/tools/compare_raptor_pimcomp.py" COMPARE = REPO / "validation/tools/compare_raptor_pimcomp.py"
MODELS = { MODELS = {
"vgg8": SUITE / "vgg8/vgg8-mnist-reconstructed.onnx", "vgg8": SUITE / "vgg8/vgg8-mnist-reconstructed.onnx",
@@ -23,38 +24,21 @@ MODELS = {
def run(command: list[str], *, dry_run: bool, check: bool = True) -> int: def run(command: list[str], *, dry_run: bool, check: bool = True) -> int:
print(f"$ {shlex.join(command)}", flush=True) print(Fore.CYAN + "$ " + Style.RESET_ALL + shlex.join(command), flush=True)
if dry_run: if dry_run:
return 0 return 0
return subprocess.run(command, cwd=REPO, check=check).returncode return subprocess.run(command, cwd=REPO, check=check).returncode
def prepare_pimcomp(work_dir: Path) -> None: def validate_pimcomp_source() -> None:
shutil.copytree( header = PIMCOMP_SOURCE / "backend/GeneticAlgorithm.h"
PIMCOMP_SOURCE,
work_dir,
dirs_exist_ok=True,
ignore=shutil.ignore_patterns(".git", "build", "output"),
)
header = work_dir / "backend/GeneticAlgorithm.h"
source = header.read_text(encoding="utf-8") source = header.read_text(encoding="utf-8")
if "int population_num = 200;" not in source: for setting in ("int population_num = 200;", "int max_iteration = 1000;"):
raise RuntimeError("PIMCOMP GA population is not 200") if setting not in source:
source, replacements = re.subn( raise RuntimeError(f"PIMCOMP paper setting is missing: {setting}")
r"int max_iteration = \d+;",
"int max_iteration = 1000;",
source,
)
if replacements != 1:
raise RuntimeError("Could not set PIMCOMP GA max_iteration")
header.write_text(source, encoding="utf-8")
shutil.copy2(
REPO / "validation/pimsim_configs/pimcomp/arch-a/latency_config.json",
work_dir / "config.json",
)
def comparison_command(model: Path, result_dir: Path, pimcomp_dir: Path, timeout: float) -> list[str]: def comparison_command(model: Path, result_dir: Path, timeout: float) -> list[str]:
return [ return [
sys.executable, sys.executable,
str(COMPARE), str(COMPARE),
@@ -63,7 +47,9 @@ def comparison_command(model: Path, result_dir: Path, pimcomp_dir: Path, timeout
"--out-dir", "--out-dir",
str(result_dir), str(result_dir),
"--pimcomp-dir", "--pimcomp-dir",
str(pimcomp_dir), str(PIMCOMP_SOURCE),
"--pimcomp-config",
str(PIMCOMP_CONFIG),
"--core-count", "--core-count",
"168", "168",
"--crossbar-count", "--crossbar-count",
@@ -82,6 +68,7 @@ def comparison_command(model: Path, result_dir: Path, pimcomp_dir: Path, timeout
"GA", "GA",
"--timeout-seconds", "--timeout-seconds",
str(timeout), str(timeout),
"--fail-on-error",
] ]
@@ -89,51 +76,53 @@ def main() -> int:
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Reproduce the serial Arch-A latency comparison from the PIMCOMP paper." description="Reproduce the serial Arch-A latency comparison from the PIMCOMP paper."
) )
parser.add_argument("--out-dir", required=True, type=Path) parser.add_argument(
"--out-dir",
type=Path,
help="Result root (default: artifacts beside each model under validation/).",
)
parser.add_argument("--models", nargs="+", choices=MODELS, default=list(MODELS)) parser.add_argument("--models", nargs="+", choices=MODELS, default=list(MODELS))
parser.add_argument("--timeout-seconds", type=float, default=3600.0) parser.add_argument("--timeout-seconds", type=float, default=3600.0)
parser.add_argument( parser.add_argument(
"--resume", "--resume",
action="store_true", action="store_true",
help="Keep the existing work tree and skip models with a completed JSON report.", help="Skip models with a completed JSON report.",
) )
parser.add_argument("--dry-run", action="store_true", help="Print commands without modifying files.") parser.add_argument("--dry-run", action="store_true", help="Print commands without modifying files.")
args = parser.parse_args() args = parser.parse_args()
out_dir = args.out_dir.resolve() out_dir = args.out_dir.resolve() if args.out_dir is not None else None
work_dir = out_dir / "pimcomp-ga1000"
if not args.dry_run and out_dir.exists() and any(out_dir.iterdir()) and not args.resume:
parser.error(f"{out_dir} is not empty; choose a fresh directory or pass --resume")
missing = [str(MODELS[name]) for name in args.models if not MODELS[name].exists()] missing = [str(MODELS[name]) for name in args.models if not MODELS[name].exists()]
if missing: if missing:
parser.error(f"missing model(s): {', '.join(missing)}") parser.error(f"missing model(s): {', '.join(missing)}")
if args.dry_run: validate_pimcomp_source()
print(f"# prepare isolated PIMCOMP GA build in {work_dir}") if out_dir is not None and not args.dry_run:
else:
out_dir.mkdir(parents=True, exist_ok=True) out_dir.mkdir(parents=True, exist_ok=True)
prepare_pimcomp(work_dir)
run(["cmake", "--build", str(REPO / "build_release")], dry_run=args.dry_run) run(["cmake", "--build", str(REPO / "build_release")], dry_run=args.dry_run)
run( run(
["cmake", "-S", str(work_dir), "-B", str(work_dir / "build")], ["cmake", "--build", str(PIMCOMP_SOURCE / "build"), "--target", "PIMCOMP-NN"],
dry_run=args.dry_run,
)
run(
["cmake", "--build", str(work_dir / "build"), "--target", "PIMCOMP-NN"],
dry_run=args.dry_run, dry_run=args.dry_run,
) )
failed = [] failed = []
for name in args.models: for name in args.models:
result_dir = out_dir / name result_dir = out_dir / name if out_dir is not None else MODELS[name].parent
if args.resume and (result_dir / "comparison_report.json").exists(): if args.resume and (result_dir / "pimcomp/comparison_report.json").exists():
print(f"[{name}] completed report exists; skipping", flush=True) print(
Fore.YELLOW + f"[{name}] completed report exists; skipping" + Style.RESET_ALL,
flush=True,
)
continue continue
print(f"\n[{name}] Arch-A latency comparison", flush=True) print(
"\n" + Fore.CYAN + f"[{name}]" + Style.RESET_ALL
+ f" {Style.BRIGHT}Arch-A latency comparison{Style.RESET_ALL}",
flush=True,
)
returncode = run( returncode = run(
comparison_command(MODELS[name], result_dir, work_dir, args.timeout_seconds), comparison_command(MODELS[name], result_dir, args.timeout_seconds),
dry_run=args.dry_run, dry_run=args.dry_run,
check=False, check=False,
) )
@@ -141,8 +130,17 @@ def main() -> int:
failed.append(name) failed.append(name)
if failed: if failed:
print(f"\nCompleted with failed comparisons: {', '.join(failed)}", file=sys.stderr) print(
"\n" + Style.BRIGHT + Fore.RED + "Result: FAIL" + Style.RESET_ALL,
file=sys.stderr,
)
print(
Fore.RED + f"Failed comparisons: {', '.join(failed)}" + Style.RESET_ALL,
file=sys.stderr,
)
return 1 return 1
if not args.dry_run:
print("\n" + Style.BRIGHT + f"Result: {Fore.GREEN}PASS" + Style.RESET_ALL)
return 0 return 0