add ablation study
Validate Operations / validate-operations (push) Has been cancelled

normalize names and artifact paths
This commit is contained in:
NiccoloN
2026-08-20 17:58:02 +02:00
parent add20e56eb
commit b009e1ff08
67 changed files with 1573 additions and 993 deletions
@@ -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.
@@ -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:
@@ -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