normalize names and artifact paths
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
# Pimcomp model comparison
|
||||
|
||||
These scripts compare Raptor with Pimcomp on the models in
|
||||
`validation/networks/pimcomp_models`.
|
||||
|
||||
`run_pimcomp_models.py` runs the supported model suite across the configured
|
||||
architectures and simulation modes. `compare_raptor_pimcomp_model.py` is the
|
||||
lower-level one-model comparison used by the suite runner.
|
||||
|
||||
## Suite runner
|
||||
|
||||
Run all five models, Arch-A and Arch-B, latency, and throughput:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/run_pimcomp_models.py
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Option | Description and default |
|
||||
|---|---|
|
||||
| `-h`, `--help` | Show help and exit. |
|
||||
| `--out-dir PATH` | Suite root; artifacts are placed below `<out-dir>/<model>/artifacts/`. Default: `validation/networks/pimcomp_models`. |
|
||||
| `--common-dir PATH` | Shared root for per-model reference artifacts. Default: `<out-dir>/<model>/artifacts/common`. |
|
||||
| `--models MODEL [...]` | Models to run: `vgg8`, `resnet18`, `resnet34`, `googlenet`, `yolo11n`. Default: all five; pass a subset to select specific models. |
|
||||
| `--archs ARCH [...]` | Pimcomp hardware profiles to run. Default: `arch-a arch-b`. |
|
||||
| `--mode {latency,throughput} [...]` | Simulation modes. Default: both. |
|
||||
| `--only {raptor,pimcomp}` | Re-run only one compiler side and reuse the other side's existing report. Default: run both sides. |
|
||||
| `--raptor-only` | Run Raptor without compiling, validating, or simulating Pimcomp. Default: off. |
|
||||
| `--pipeline {1,2,4,8}` | Select one Raptor pipeline. Default: latency pipeline 1 and throughput pipelines 2, 4, and 8. |
|
||||
| `--pimsim-time-ms INT` | Throughput convergence deadline. Default: `1000`. |
|
||||
| `--batch-size INT` | Functional throughput batch size. Default: `128`. |
|
||||
| `--timeout-seconds FLOAT` | Per-stage timeout; `0` means unlimited. Default: `0`. |
|
||||
| `-j INT`, `--jobs INT` | Parallel comparison workers. Default: `4`. |
|
||||
| `--clean` | Remove generated comparison artifacts and summaries, then exit. Default: off. |
|
||||
| `--ablation-variant NAME` | Put generated artifacts below `<mode>[/pipelineN]/ablation/NAME` and write the summary below `ablation/NAME`. Default: none. |
|
||||
| `--dry-run` | Print commands without modifying files. Default: off. |
|
||||
| `--no-fast` | Disable fast throughput convergence. Default: off. |
|
||||
| `--raptor-extra-arg=ARG` | Extra Raptor compiler argument; repeat for multiple arguments. Default: none. |
|
||||
|
||||
Arguments beginning with `--` must use the equals form when passed through:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/compare/run_pimcomp_models.py \
|
||||
--models vgg8 \
|
||||
--raptor-extra-arg=--pim-disable-synchronization
|
||||
```
|
||||
|
||||
The runner writes `results_comparison.csv` under the selected result root. It reuses
|
||||
shared model inputs, outputs, and reference runners, and reuses Pimcomp
|
||||
artifacts between pipelines in the same model/architecture/mode group. With
|
||||
`--raptor-only`, only Raptor results are generated and Pimcomp is not invoked.
|
||||
All generated files are kept below each model's `artifacts/` directory;
|
||||
`--clean` also removes any `.lock` files left by an interrupted reference
|
||||
generation.
|
||||
|
||||
## One-model comparator
|
||||
|
||||
`compare_raptor_pimcomp_model.py` compares one ONNX model. Its required
|
||||
arguments are `--model PATH` and `--out-dir PATH`; use `--help` for the full
|
||||
lower-level interface. The suite runner supplies the model, hardware profile,
|
||||
simulation mode, and reuse paths automatically.
|
||||
+243
-150
@@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import fcntl
|
||||
import gzip
|
||||
import importlib.util
|
||||
import json
|
||||
@@ -30,8 +31,8 @@ PIMSIM_CONFIG_DIR = VALIDATION_DIR / "pimsim_configs/pimcomp"
|
||||
PIMCOMP_OUTPUT_FILES = ("SimulationInfo.gz", "VerificationInfo.json", "MappingResult.txt")
|
||||
sys.path.insert(0, str(VALIDATION_DIR))
|
||||
|
||||
from raptor_validation.gen_network_runner import gen_network_runner # noqa: E402
|
||||
from raptor_validation.onnx_utils import ( # noqa: E402
|
||||
from raptor_validation.gen_network_runner import gen_network_runner
|
||||
from raptor_validation.onnx_utils import (
|
||||
_ONNX_TO_NP,
|
||||
generate_input_batch,
|
||||
gen_random_inputs,
|
||||
@@ -41,12 +42,13 @@ from raptor_validation.onnx_utils import ( # noqa: E402
|
||||
write_input_batch_csv,
|
||||
write_inputs_to_memory_bin,
|
||||
)
|
||||
from raptor_validation.raptor import compile_with_raptor # noqa: E402
|
||||
from raptor_validation.pimsim_nn import ( # noqa: E402
|
||||
from raptor_validation.raptor import compile_with_raptor
|
||||
from raptor_validation.pimsim_nn import (
|
||||
export_raptor_pimsim_artifact,
|
||||
parse_pimsim_nn_metrics,
|
||||
)
|
||||
from raptor_validation.validate_one import ( # noqa: E402
|
||||
from raptor_validation.artifacts import runner_uses_library
|
||||
from raptor_validation.validate_one import (
|
||||
STAGE_COLORS,
|
||||
build_pim_simulator_command,
|
||||
build_dump_ranges,
|
||||
@@ -99,7 +101,7 @@ def print_step(
|
||||
stage: str | None = None,
|
||||
):
|
||||
color = STAGE_COLORS.get(stage or name, Fore.WHITE)
|
||||
print("\n" + Style.BRIGHT + color + f"[{name}]" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + color + f"[{name}]" + Style.RESET_ALL)
|
||||
if cmd is not None:
|
||||
print(f" cwd: {cwd or REPO}")
|
||||
print(f" $ {shell_join(cmd)}")
|
||||
@@ -134,7 +136,7 @@ def exception_message(exc: BaseException) -> str:
|
||||
def print_failure(name: str, exc: BaseException | str) -> None:
|
||||
message = exc if isinstance(exc, str) else exception_message(exc)
|
||||
print(
|
||||
"\n" + Style.BRIGHT + Fore.RED + f"[{name} FAILED]" + Style.RESET_ALL,
|
||||
Style.BRIGHT + Fore.RED + f"[{name} FAILED]" + Style.RESET_ALL,
|
||||
file=sys.stderr,
|
||||
)
|
||||
for line in message.splitlines()[:20]:
|
||||
@@ -239,6 +241,17 @@ def reference_inputs_exist(
|
||||
)
|
||||
|
||||
|
||||
def reference_batch_dirs(root: Path, batch_size: int) -> list[Path]:
|
||||
return [root / f"batch_{index:06d}" for index in range(batch_size)]
|
||||
|
||||
|
||||
def reference_batch_outputs_exist(
|
||||
outputs_desc: list[tuple[int, str, int, list[int]]],
|
||||
reference_dirs: list[Path],
|
||||
) -> bool:
|
||||
return all(reference_outputs_exist(outputs_desc, reference_dir) for reference_dir in reference_dirs)
|
||||
|
||||
|
||||
def compare_simulator_outputs(
|
||||
output_bin: Path,
|
||||
outputs_desc: list[tuple[int, str, int, list[int]]],
|
||||
@@ -308,7 +321,7 @@ def select_pimsim_config(args: argparse.Namespace, hardware: dict[str, int]) ->
|
||||
if args.pimsim_mode == "latency" or config["sim_config"]["sim_time"] == args.pimsim_time_ms:
|
||||
return path
|
||||
raise ValueError(
|
||||
f"No pre-generated {args.pimsim_mode} pimsim-nn config for {args.pimsim_time_ms} ms matches {hardware}"
|
||||
f"No pre-generated {args.pimsim_mode} Pimsim config for {args.pimsim_time_ms} ms matches {hardware}"
|
||||
)
|
||||
|
||||
|
||||
@@ -329,13 +342,14 @@ def compile_reference(
|
||||
runner_dir = work_dir / "runner"
|
||||
build_dir = runner_dir / "build"
|
||||
raptor_dir.mkdir(parents=True, exist_ok=True)
|
||||
shutil.rmtree(build_dir, ignore_errors=True)
|
||||
build_dir.mkdir(parents=True, exist_ok=True)
|
||||
stem = model_path.stem
|
||||
onnx_ir_base = raptor_dir / stem
|
||||
runner_base = runner_dir / stem
|
||||
|
||||
run_logged(
|
||||
"Compile Reference ONNX IR",
|
||||
"Compile reference ONNX IR",
|
||||
[str(args.raptor_path), str(model_path), "-o", str(onnx_ir_base), "--EmitONNXIR",
|
||||
"--mlir-elide-elementsattrs-if-larger=16", "--enable-conv-opt-pass=false"],
|
||||
cwd=REPO,
|
||||
@@ -344,7 +358,7 @@ def compile_reference(
|
||||
stage="Compile ONNX",
|
||||
)
|
||||
run_logged(
|
||||
"Compile Reference Native",
|
||||
"Compile reference native",
|
||||
[str(args.raptor_path), "-O3", str(model_path), "-o", str(runner_base)],
|
||||
cwd=REPO,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
@@ -353,7 +367,7 @@ def compile_reference(
|
||||
)
|
||||
network_so = runner_base.with_suffix(".so")
|
||||
|
||||
print_step("Generate Runner Source", stage="Build Runner")
|
||||
print_step("Generate runner source", stage="Build runner")
|
||||
gen_network_runner(
|
||||
model_path,
|
||||
network_so,
|
||||
@@ -364,15 +378,15 @@ def compile_reference(
|
||||
)
|
||||
|
||||
run_logged(
|
||||
"Configure Runner",
|
||||
"Configure runner",
|
||||
["cmake", str(runner_dir), "-DCMAKE_BUILD_TYPE=Release", "-DCMAKE_C_FLAGS_RELEASE=-O3"],
|
||||
cwd=build_dir,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
steps=steps,
|
||||
stage="Build Runner",
|
||||
stage="Build runner",
|
||||
)
|
||||
run_logged(
|
||||
"Build Runner",
|
||||
"Build runner",
|
||||
["cmake", "--build", ".", "-j"],
|
||||
cwd=build_dir,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
@@ -382,6 +396,20 @@ def compile_reference(
|
||||
return build_dir / "runner"
|
||||
|
||||
|
||||
def ensure_reference_runner(
|
||||
args: argparse.Namespace,
|
||||
model_path: Path,
|
||||
work_dir: Path,
|
||||
steps: list[StepRecord],
|
||||
) -> Path:
|
||||
runner_dir = work_dir / "runner"
|
||||
runner_path = runner_dir / "build/runner"
|
||||
library_path = runner_dir / f"{model_path.stem}.so"
|
||||
if runner_uses_library(runner_path, library_path):
|
||||
return runner_path
|
||||
return compile_reference(args, model_path, work_dir, steps)
|
||||
|
||||
|
||||
def generate_reference_outputs(
|
||||
runner_path: Path,
|
||||
runner_build_dir: Path,
|
||||
@@ -390,6 +418,8 @@ def generate_reference_outputs(
|
||||
steps: list[StepRecord],
|
||||
args: argparse.Namespace,
|
||||
out_dir: Path,
|
||||
*,
|
||||
print_header: bool = True,
|
||||
) -> Path:
|
||||
inputs_dir = out_dir / "inputs"
|
||||
reference_dir = out_dir / "outputs"
|
||||
@@ -397,11 +427,12 @@ def generate_reference_outputs(
|
||||
reference_dir.mkdir(parents=True, exist_ok=True)
|
||||
flags, _ = save_inputs_to_files(model_path, arrays_in_order, inputs_dir)
|
||||
run_logged(
|
||||
"Run Reference",
|
||||
"Run reference",
|
||||
[str(runner_path), *flags, "--save-csv-dir", str(reference_dir)],
|
||||
cwd=runner_build_dir,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
steps=steps,
|
||||
print_header=print_header,
|
||||
)
|
||||
return reference_dir
|
||||
|
||||
@@ -414,19 +445,56 @@ def generate_reference_batch_outputs(
|
||||
steps: list[StepRecord],
|
||||
args: argparse.Namespace,
|
||||
out_dir: Path,
|
||||
*,
|
||||
print_header: bool = True,
|
||||
) -> list[Path]:
|
||||
return [
|
||||
generate_reference_outputs(
|
||||
if print_header:
|
||||
print_step("Run reference")
|
||||
references = []
|
||||
for index, sample in enumerate(input_batch):
|
||||
references.append(
|
||||
generate_reference_outputs(
|
||||
runner_path,
|
||||
runner_build_dir,
|
||||
model_path,
|
||||
sample,
|
||||
steps,
|
||||
args,
|
||||
out_dir / f"batch_{index:06d}",
|
||||
print_header=False,
|
||||
)
|
||||
)
|
||||
return references
|
||||
|
||||
|
||||
def prepare_reference_batch_outputs(
|
||||
runner_path: Path,
|
||||
runner_build_dir: Path,
|
||||
model_path: Path,
|
||||
input_batch: list[list[np.ndarray]],
|
||||
outputs_desc: list[tuple[int, str, int, list[int]]],
|
||||
steps: list[StepRecord],
|
||||
args: argparse.Namespace,
|
||||
out_dir: Path,
|
||||
*,
|
||||
print_header: bool = True,
|
||||
) -> list[Path]:
|
||||
reference_dirs = reference_batch_dirs(out_dir, len(input_batch))
|
||||
lock_path = out_dir.parent / ".reference.lock"
|
||||
with lock_path.open("w", encoding="utf-8") as lock:
|
||||
fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
|
||||
if reference_batch_outputs_exist(outputs_desc, reference_dirs):
|
||||
return reference_dirs
|
||||
return generate_reference_batch_outputs(
|
||||
runner_path,
|
||||
runner_build_dir,
|
||||
model_path,
|
||||
sample,
|
||||
input_batch,
|
||||
steps,
|
||||
args,
|
||||
out_dir / f"batch_{index:06d}",
|
||||
out_dir,
|
||||
print_header=print_header,
|
||||
)
|
||||
for index, sample in enumerate(input_batch)
|
||||
]
|
||||
|
||||
|
||||
def prepare_common_artifacts(
|
||||
@@ -437,10 +505,8 @@ def prepare_common_artifacts(
|
||||
inputs_desc, outputs_desc, arrays_in_order = load_model_inputs(model_path, args.seed)
|
||||
inputs_dir = common_dir / "inputs"
|
||||
outputs_dir = common_dir / "outputs"
|
||||
runner_path = common_dir / "runner/build/runner"
|
||||
steps: list[StepRecord] = []
|
||||
if not runner_path.exists():
|
||||
runner_path = compile_reference(args, model_path, common_dir, steps)
|
||||
runner_path = ensure_reference_runner(args, model_path, common_dir, steps)
|
||||
|
||||
inputs_ready = reference_inputs_exist(inputs_desc, inputs_dir)
|
||||
outputs_ready = reference_outputs_exist(outputs_desc, outputs_dir)
|
||||
@@ -478,7 +544,7 @@ def compile_raptor_target(
|
||||
"--pim-emit-json",
|
||||
*args.raptor_extra_arg,
|
||||
]
|
||||
print_step("Compile Raptor", cmd, REPO, "Compile PIM")
|
||||
print_step("Compile Raptor", cmd, REPO, "Compile Pim")
|
||||
start = time.perf_counter()
|
||||
command = shell_join(cmd)
|
||||
raptor_extra_args = [
|
||||
@@ -503,7 +569,7 @@ def compile_raptor_target(
|
||||
except Exception as exc:
|
||||
steps.append(
|
||||
StepRecord(
|
||||
name="Compile Raptor PIM",
|
||||
name="Compile Raptor Pim",
|
||||
duration_sec=time.perf_counter() - start,
|
||||
command=command,
|
||||
status="failed",
|
||||
@@ -514,7 +580,7 @@ def compile_raptor_target(
|
||||
|
||||
steps.append(
|
||||
StepRecord(
|
||||
name="Compile Raptor PIM",
|
||||
name="Compile Raptor Pim",
|
||||
duration_sec=time.perf_counter() - start,
|
||||
command=command,
|
||||
)
|
||||
@@ -548,7 +614,8 @@ def run_functional_validation(
|
||||
pim_dir,
|
||||
output_bin,
|
||||
dump_ranges,
|
||||
input_bins,
|
||||
input_bins[0].parent,
|
||||
batch_size,
|
||||
args.pimsim_mode,
|
||||
batch_output_dir,
|
||||
)
|
||||
@@ -559,7 +626,7 @@ def run_functional_validation(
|
||||
cwd=args.pim_simulator_dir,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
steps=steps,
|
||||
stage="Run Functional Simulation",
|
||||
stage="Run functional simulation",
|
||||
)
|
||||
max_diffs: dict[str, float] = {}
|
||||
failed_iterations = []
|
||||
@@ -610,9 +677,9 @@ def compile_pimcomp(
|
||||
(pimcomp_output_dir / name).unlink(missing_ok=True)
|
||||
model_name = args.pimcomp_model_name or f"compare_{model_path.stem}"
|
||||
frontend_json = frontend_json_dir / f"{model_name}.json"
|
||||
print_step("Compile PIMCOMP", stage="Compile PIM")
|
||||
# The original PIMCOMP frontend rewrites its input ONNX while loading it.
|
||||
# Isolate that mutation without changing the model sent through PIMCOMP.
|
||||
print_step("Compile Pimcomp", stage="Compile Pim")
|
||||
# The original Pimcomp frontend rewrites its input ONNX while loading it.
|
||||
# Isolate that mutation without changing the model sent through Pimcomp.
|
||||
with TemporaryDirectory(prefix="pimcomp-model-") as temp_dir:
|
||||
frontend_model = Path(temp_dir) / model_path.name
|
||||
shutil.copy2(model_path, frontend_model)
|
||||
@@ -625,12 +692,12 @@ def compile_pimcomp(
|
||||
str(frontend_json),
|
||||
]
|
||||
run_logged(
|
||||
"Compile PIMCOMP Frontend",
|
||||
"Compile Pimcomp frontend",
|
||||
frontend_cmd,
|
||||
cwd=args.pimcomp_dir / "frontend",
|
||||
timeout_sec=args.timeout_seconds,
|
||||
steps=steps,
|
||||
stage="Compile PIM",
|
||||
stage="Compile Pim",
|
||||
print_header=False,
|
||||
)
|
||||
backend_cmd = [
|
||||
@@ -643,12 +710,12 @@ def compile_pimcomp(
|
||||
]
|
||||
try:
|
||||
run_logged(
|
||||
"Compile PIMCOMP Backend",
|
||||
"Compile Pimcomp backend",
|
||||
backend_cmd,
|
||||
cwd=frontend_json_dir.parent,
|
||||
timeout_sec=args.timeout_seconds,
|
||||
steps=steps,
|
||||
stage="Compile PIM",
|
||||
stage="Compile Pim",
|
||||
print_header=False,
|
||||
)
|
||||
finally:
|
||||
@@ -666,7 +733,7 @@ def export_pimcomp_for_pimsim_nn(simulation_info: Path, output_dir: Path) -> Pat
|
||||
sim_config = sim_info["config"]
|
||||
core_count = int(sim_config["core_cnt"])
|
||||
if core_count <= 0:
|
||||
raise ValueError("PIMCOMP SimulationInfo.gz must configure at least one core")
|
||||
raise ValueError("Pimcomp SimulationInfo.gz must configure at least one core")
|
||||
core_indices = range(core_count)
|
||||
|
||||
config = {
|
||||
@@ -709,7 +776,7 @@ def export_pimcomp_for_rust(
|
||||
output_dir: Path,
|
||||
) -> Path:
|
||||
if len(runtime_inputs) != 1:
|
||||
raise ValueError("PIMCOMP export currently requires exactly one runtime input tensor")
|
||||
raise ValueError("Pimcomp export currently requires exactly one runtime input tensor")
|
||||
if output_dir.exists():
|
||||
shutil.rmtree(output_dir)
|
||||
exporter = load_pimcomp_exporter()
|
||||
@@ -901,7 +968,7 @@ def export_pimcomp_for_rust(
|
||||
instructions.append(translated)
|
||||
out_offset += width
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported PIMCOMP op {op}")
|
||||
raise RuntimeError(f"Unsupported Pimcomp op {op}")
|
||||
|
||||
with open(output_dir / f"core_{core_idx}.json", "w", encoding="utf-8") as f:
|
||||
json.dump(instructions, f, separators=(",", ":"))
|
||||
@@ -935,7 +1002,7 @@ def run_pimsim_nn(
|
||||
cwd=args.pimsim_nn_build_dir,
|
||||
timeout_sec=args.timeout_seconds * 10.0,
|
||||
steps=steps,
|
||||
stage="Run Non-functional Simulation",
|
||||
stage="Run non-functional simulation",
|
||||
)
|
||||
return parse_pimsim_nn_metrics(output)
|
||||
|
||||
@@ -1091,12 +1158,9 @@ def restore_side_records(
|
||||
|
||||
|
||||
def default_common_dir(out_dir: Path) -> Path:
|
||||
if out_dir.name == "latency":
|
||||
return out_dir.parents[1] / "common"
|
||||
if out_dir.parent.name == "throughput":
|
||||
return out_dir.parents[2] / "common"
|
||||
if out_dir.name.startswith("arch-"):
|
||||
return out_dir.parent / "common"
|
||||
for parent in (out_dir, *out_dir.parents):
|
||||
if parent.name == "artifacts":
|
||||
return parent / "common"
|
||||
return out_dir / "common"
|
||||
|
||||
|
||||
@@ -1155,19 +1219,19 @@ def write_report(
|
||||
):
|
||||
report_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
lines = [
|
||||
"# Raptor vs PIMCOMP Comparison Report",
|
||||
"# Raptor vs Pimcomp comparison report",
|
||||
"",
|
||||
f"- Model: `{model_path}`",
|
||||
f"- Hardware: `{hardware.get('core_count', 'n/a')} cores`, `{hardware.get('crossbar_count', 'n/a')} xbars/core`, `{hardware.get('crossbar_size', 'n/a')}x{hardware.get('crossbar_size', 'n/a')}` crossbars, mesh `{hardware.get('mesh_rows', 'n/a')}x{hardware.get('mesh_cols', 'n/a')}`",
|
||||
f"- PIMCOMP pipeline: `{pimcomp_pipeline}`",
|
||||
f"- PIMCOMP replication: `{pimcomp_replication}`",
|
||||
f"- Pimcomp pipeline: `{pimcomp_pipeline}`",
|
||||
f"- Pimcomp replication: `{pimcomp_replication}`",
|
||||
"",
|
||||
]
|
||||
|
||||
if failures or any(step.status != "passed" for step in steps):
|
||||
lines.extend(
|
||||
[
|
||||
"## Failures / Skipped Work",
|
||||
"## Failures / skipped work",
|
||||
"",
|
||||
"The script did not abort. The failed stage was recorded and any dependent stage was skipped when its inputs were not available.",
|
||||
"",
|
||||
@@ -1182,21 +1246,21 @@ def write_report(
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"## Functional Validation",
|
||||
"## Functional validation",
|
||||
"",
|
||||
f"- Raptor via `pim-simulator`: `{validation_status(raptor_validation)}`",
|
||||
f"- PIMCOMP via exported `pim-simulator`: `{validation_status(pimcomp_validation)}`",
|
||||
f"- Pimcomp via exported `pim-simulator`: `{validation_status(pimcomp_validation)}`",
|
||||
]
|
||||
)
|
||||
if raptor_validation.error:
|
||||
lines.append(f"- Raptor validation note: `{raptor_validation.error.splitlines()[0]}`")
|
||||
if pimcomp_validation.error:
|
||||
lines.append(f"- PIMCOMP validation note: `{pimcomp_validation.error.splitlines()[0]}`")
|
||||
lines.append(f"- Pimcomp validation note: `{pimcomp_validation.error.splitlines()[0]}`")
|
||||
|
||||
lines.extend(["", "### Max Output Differences", ""])
|
||||
lines.extend(["", "### Max output differences", ""])
|
||||
diff_names = sorted(set(raptor_validation.max_diffs) | set(pimcomp_validation.max_diffs))
|
||||
if diff_names:
|
||||
lines.extend(["| Output | Raptor max diff | PIMCOMP max diff |", "|---|---:|---:|"])
|
||||
lines.extend(["| Output | Raptor max diff | Pimcomp max diff |", "|---|---:|---:|"])
|
||||
for name in diff_names:
|
||||
lines.append(
|
||||
f"| `{name}` | {raptor_validation.max_diffs.get(name, float('nan')):.6e} | "
|
||||
@@ -1208,7 +1272,7 @@ def write_report(
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
"## pimsim-nn Performance",
|
||||
"## Pimsim performance",
|
||||
"",
|
||||
f"- Mode: `{pimsim_mode}`",
|
||||
"",
|
||||
@@ -1221,7 +1285,7 @@ def write_report(
|
||||
"|---|---|---:|---:|---:|---:|---:|",
|
||||
f"| Raptor | {perf_status(raptor_perf)} | {perf_value(raptor_perf, 'average_latency_ms', 'ms')} | {perf_value(raptor_perf, 'throughput', 'samples/s')} | "
|
||||
f"{perf_value(raptor_perf, 'average_power_mw', 'mW')} | {perf_value(raptor_perf, 'average_energy_pj', 'pJ/it')} | {perf_value(raptor_perf, 'output_count')} |",
|
||||
f"| PIMCOMP | {perf_status(pimcomp_perf)} | {perf_value(pimcomp_perf, 'average_latency_ms', 'ms')} | {perf_value(pimcomp_perf, 'throughput', 'samples/s')} | "
|
||||
f"| Pimcomp | {perf_status(pimcomp_perf)} | {perf_value(pimcomp_perf, 'average_latency_ms', 'ms')} | {perf_value(pimcomp_perf, 'throughput', 'samples/s')} | "
|
||||
f"{perf_value(pimcomp_perf, 'average_power_mw', 'mW')} | {perf_value(pimcomp_perf, 'average_energy_pj', 'pJ/it')} | {perf_value(pimcomp_perf, 'output_count')} |",
|
||||
"",
|
||||
]
|
||||
@@ -1233,40 +1297,40 @@ def write_report(
|
||||
"|---|---|---:|---:|---:|",
|
||||
f"| Raptor | {perf_status(raptor_perf)} | {perf_value(raptor_perf, 'latency_ms', 'ms')} | "
|
||||
f"{perf_value(raptor_perf, 'average_power_mw', 'mW')} | {perf_value(raptor_perf, 'average_energy_pj', 'pJ')} |",
|
||||
f"| PIMCOMP | {perf_status(pimcomp_perf)} | {perf_value(pimcomp_perf, 'latency_ms', 'ms')} | "
|
||||
f"| Pimcomp | {perf_status(pimcomp_perf)} | {perf_value(pimcomp_perf, 'latency_ms', 'ms')} | "
|
||||
f"{perf_value(pimcomp_perf, 'average_power_mw', 'mW')} | {perf_value(pimcomp_perf, 'average_energy_pj', 'pJ')} |",
|
||||
"",
|
||||
]
|
||||
)
|
||||
if raptor_perf.get("reason") or raptor_perf.get("error"):
|
||||
lines.append(f"- Raptor pimsim-nn note: `{(raptor_perf.get('reason') or raptor_perf.get('error')).splitlines()[0]}`")
|
||||
lines.append(f"- Raptor Pimsim note: `{(raptor_perf.get('reason') or raptor_perf.get('error')).splitlines()[0]}`")
|
||||
if pimcomp_perf.get("reason") or pimcomp_perf.get("error"):
|
||||
lines.append(f"- PIMCOMP pimsim-nn note: `{(pimcomp_perf.get('reason') or pimcomp_perf.get('error')).splitlines()[0]}`")
|
||||
lines.append(f"- Pimcomp Pimsim note: `{(pimcomp_perf.get('reason') or pimcomp_perf.get('error')).splitlines()[0]}`")
|
||||
if lines[-1] != "":
|
||||
lines.append("")
|
||||
|
||||
lines.extend(
|
||||
[
|
||||
"## Instruction Summary",
|
||||
"## Instruction summary",
|
||||
"",
|
||||
"| Compiler | Status | Active cores | Total instructions | Sends | Receives | MVMUL |",
|
||||
"|---|---|---:|---:|---:|---:|---:|",
|
||||
f"| Raptor | {'FAILED' if raptor_instr.get('error') else 'SKIPPED' if raptor_instr.get('skipped') else 'DONE'} | {raptor_instr.get('active_cores', 0)} | {raptor_instr.get('total_instructions', 0)} | {raptor_instr.get('op_counts', {}).get('send', 0)} | {raptor_instr.get('op_counts', {}).get('recv', 0)} | {raptor_instr.get('op_counts', {}).get('mvmul', 0)} |",
|
||||
f"| PIMCOMP | {'FAILED' if pimcomp_instr.get('error') else 'SKIPPED' if pimcomp_instr.get('skipped') else 'DONE'} | {pimcomp_instr.get('active_cores', 0)} | {pimcomp_instr.get('total_instructions', 0)} | {pimcomp_instr.get('op_counts', {}).get('send', 0)} | {pimcomp_instr.get('op_counts', {}).get('recv', 0)} | {pimcomp_instr.get('op_counts', {}).get('mvmul', 0)} |",
|
||||
f"| Pimcomp | {'FAILED' if pimcomp_instr.get('error') else 'SKIPPED' if pimcomp_instr.get('skipped') else 'DONE'} | {pimcomp_instr.get('active_cores', 0)} | {pimcomp_instr.get('total_instructions', 0)} | {pimcomp_instr.get('op_counts', {}).get('send', 0)} | {pimcomp_instr.get('op_counts', {}).get('recv', 0)} | {pimcomp_instr.get('op_counts', {}).get('mvmul', 0)} |",
|
||||
"",
|
||||
"### Raptor Op Distribution",
|
||||
"### Raptor op distribution",
|
||||
"",
|
||||
"| Op | Count | Share |",
|
||||
"|---|---:|---:|",
|
||||
*format_op_table(raptor_instr.get("op_counts", {}), raptor_instr.get("total_instructions", 0)),
|
||||
"",
|
||||
"### PIMCOMP Op Distribution",
|
||||
"### Pimcomp op distribution",
|
||||
"",
|
||||
"| Op | Count | Share |",
|
||||
"|---|---:|---:|",
|
||||
*format_op_table(pimcomp_instr.get("op_counts", {}), pimcomp_instr.get("total_instructions", 0)),
|
||||
"",
|
||||
"## Step Timings",
|
||||
"## Step timings",
|
||||
"",
|
||||
"| Step | Status | Duration (s) | Return code |",
|
||||
"|---|---|---:|---:|",
|
||||
@@ -1279,7 +1343,7 @@ def write_report(
|
||||
)
|
||||
failed_steps = [step for step in steps if step.status != "passed"]
|
||||
if failed_steps:
|
||||
lines.extend(["", "### Failed Step Details", ""])
|
||||
lines.extend(["", "### Failed step details", ""])
|
||||
for step in failed_steps:
|
||||
lines.extend(
|
||||
[
|
||||
@@ -1294,7 +1358,7 @@ def write_report(
|
||||
lines.append("")
|
||||
|
||||
if raptor_pass_timings:
|
||||
lines.extend(["", "## Raptor Pass Timings", "", "| Pass | Duration (s) |", "|---|---:|"])
|
||||
lines.extend(["", "## Raptor pass timings", "", "| Pass | Duration (s) |", "|---|---:|"])
|
||||
for name, duration in raptor_pass_timings.items():
|
||||
lines.append(f"| {name} | {duration:.4f} |")
|
||||
report_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
@@ -1325,7 +1389,7 @@ def main():
|
||||
parser.add_argument(
|
||||
"--pimcomp-config",
|
||||
type=Path,
|
||||
help="PIMCOMP hardware config (default: <pimcomp-dir>/config.json).",
|
||||
help="Pimcomp hardware config (default: <pimcomp-dir>/config.json).",
|
||||
)
|
||||
parser.add_argument("--pim-simulator-dir", default=REPO / "backend-simulators/pim/pim-simulator", type=Path)
|
||||
parser.add_argument("--pimsim-nn-build-dir", default=REPO / "backend-simulators/pim/pimsim-nn/build", type=Path)
|
||||
@@ -1347,7 +1411,7 @@ def main():
|
||||
parser.add_argument("--pimsim-mode", choices=["latency", "throughput"], default="latency")
|
||||
parser.add_argument("--batch-size", type=int, default=128)
|
||||
parser.add_argument("--pimcomp-pipeline", choices=["element", "batch"])
|
||||
parser.add_argument("--pimcomp-model-name", help="Use a PIMCOMP built-in model name such as vgg16.")
|
||||
parser.add_argument("--pimcomp-model-name", help="Use a Pimcomp built-in model name such as vgg16.")
|
||||
parser.add_argument(
|
||||
"--pimcomp-replication",
|
||||
choices=["balance", "W0H0", "uniform", "GA"],
|
||||
@@ -1361,27 +1425,41 @@ def main():
|
||||
parser.add_argument(
|
||||
"--reuse-pimcomp-dir",
|
||||
type=Path,
|
||||
help="Reuse a directory containing PIMCOMP SimulationInfo.gz, VerificationInfo.json, and MappingResult.txt.",
|
||||
help="Reuse a directory containing Pimcomp SimulationInfo.gz, VerificationInfo.json, and MappingResult.txt.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--reuse-pimcomp-report",
|
||||
type=Path,
|
||||
help="Preserve the PIMCOMP side of an existing comparison report without rerunning it.",
|
||||
help="Preserve the Pimcomp side of an existing comparison report without rerunning it.",
|
||||
)
|
||||
parser.add_argument("--skip-pimsim-nn", action="store_true")
|
||||
parser.add_argument(
|
||||
"--no-fast",
|
||||
action="store_true",
|
||||
help="Disable fast pimsim-nn throughput convergence for authoritative experiments.",
|
||||
help="Disable fast Pimsim throughput convergence for authoritative experiments.",
|
||||
)
|
||||
parser.add_argument("--verbose-raptor-compile", action="store_true")
|
||||
parser.add_argument("--raptor-extra-arg", action="append", default=[])
|
||||
parser.add_argument(
|
||||
"--raptor-only",
|
||||
action="store_true",
|
||||
help="Run Raptor compilation, validation, and simulation without running Pimcomp.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--fail-on-error",
|
||||
action="store_true",
|
||||
help="Return a non-zero status if a stage or semantic validation fails.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.raptor_only and any(
|
||||
option is not None
|
||||
for option in (
|
||||
args.reuse_raptor_report,
|
||||
args.reuse_pimcomp_dir,
|
||||
args.reuse_pimcomp_report,
|
||||
)
|
||||
):
|
||||
parser.error("--raptor-only cannot be combined with report reuse options")
|
||||
if args.reuse_pimcomp_dir is not None and args.reuse_pimcomp_report is not None:
|
||||
parser.error("--reuse-pimcomp-dir and --reuse-pimcomp-report are mutually exclusive")
|
||||
if args.pimsim_time_ms <= 0:
|
||||
@@ -1439,6 +1517,7 @@ def main():
|
||||
runner_path: Path | None = None
|
||||
reference_dir: Path | None = None
|
||||
reference_dirs: list[Path] = []
|
||||
reference_stage_started = False
|
||||
raptor_pim_dir: Path | None = None
|
||||
raptor_pimsim_dir: Path | None = None
|
||||
raptor_pass_timings: dict[str, float] = {}
|
||||
@@ -1451,11 +1530,19 @@ def main():
|
||||
reuse_raptor = args.reuse_raptor_report is not None
|
||||
|
||||
raptor_validation = skipped_validation("Raptor validation did not run")
|
||||
pimcomp_validation = failed_validation("PIMCOMP validation did not run")
|
||||
raptor_perf: dict[str, Any] = skipped_perf("pimsim-nn Raptor did not run")
|
||||
pimcomp_perf: dict[str, Any] = skipped_perf("pimsim-nn PIMCOMP did not run")
|
||||
pimcomp_validation = (
|
||||
skipped_validation("Skipped by --raptor-only")
|
||||
if args.raptor_only
|
||||
else failed_validation("Pimcomp validation did not run")
|
||||
)
|
||||
raptor_perf: dict[str, Any] = skipped_perf("Pimsim Raptor did not run")
|
||||
pimcomp_perf: dict[str, Any] = skipped_perf(
|
||||
"Skipped by --raptor-only" if args.raptor_only else "Pimsim Pimcomp did not run"
|
||||
)
|
||||
raptor_instr: dict[str, Any] = empty_instruction_summary("Raptor instruction parsing did not run")
|
||||
pimcomp_instr: dict[str, Any] = empty_instruction_summary("PIMCOMP instruction parsing did not run")
|
||||
pimcomp_instr: dict[str, Any] = empty_instruction_summary(
|
||||
"Skipped by --raptor-only" if args.raptor_only else "Pimcomp instruction parsing did not run"
|
||||
)
|
||||
|
||||
loaded_hardware = try_stage(failures, "Load hardware configuration", load_effective_hardware, args)
|
||||
if loaded_hardware is not None:
|
||||
@@ -1476,22 +1563,21 @@ def main():
|
||||
if args.reuse_pimcomp_dir is not None:
|
||||
reused_pimcomp_report_path = args.reuse_pimcomp_dir.resolve().parent / "comparison_report.json"
|
||||
reuse_pimcomp = reused_pimcomp_report_path is not None
|
||||
run_pimcomp = not args.raptor_only and not reuse_pimcomp
|
||||
if reuse_pimcomp:
|
||||
reused_pimcomp_report_path = reused_pimcomp_report_path.resolve()
|
||||
if not reused_pimcomp_report_path.exists():
|
||||
raise ValueError(f"Missing PIMCOMP report: {reused_pimcomp_report_path}")
|
||||
raise ValueError(f"Missing Pimcomp report: {reused_pimcomp_report_path}")
|
||||
with open(reused_pimcomp_report_path, "r", encoding="utf-8") as f:
|
||||
reused_pimcomp = json.load(f)
|
||||
if reused_pimcomp.get("pimcomp_model_source") != "original_onnx":
|
||||
raise ValueError("Reused PIMCOMP artifacts were not generated from the original ONNX model")
|
||||
raise ValueError("Reused Pimcomp artifacts were not generated from the original ONNX model")
|
||||
pimcomp_validation = CompareResult(**reused_pimcomp["pimcomp_validation"])
|
||||
pimcomp_perf = reused_pimcomp["pimcomp_performance"]
|
||||
pimcomp_instr = reused_pimcomp["pimcomp_instruction_summary"]
|
||||
simulation_info = path_from_report(reused_pimcomp, "pimcomp_simulation_info")
|
||||
pimcomp_export_dir = path_from_report(reused_pimcomp, "pimcomp_exported_pim")
|
||||
pimcomp_pimsim_dir = path_from_report(reused_pimcomp, "pimcomp_pimsim_nn")
|
||||
if pimcomp_pimsim_dir is None and (reused_pimcomp_report_path.parent / "pimsim_nn").is_dir():
|
||||
pimcomp_pimsim_dir = reused_pimcomp_report_path.parent / "pimsim_nn"
|
||||
pimsim_config = path_from_report(reused_pimcomp, "pimsim_config")
|
||||
restore_side_records(reused_pimcomp, "pimcomp", failures, steps)
|
||||
|
||||
@@ -1504,6 +1590,9 @@ def main():
|
||||
raise ValueError(f"Reused Raptor hardware differs: {reused_hardware} != {hardware}")
|
||||
reference_dir = common_dir / "outputs"
|
||||
runner_path = path_from_report(reused, "reference_runner")
|
||||
expected_library = common_dir / "runner" / f"{functional_model_path.stem}.so"
|
||||
if runner_path is None or not runner_uses_library(runner_path, expected_library):
|
||||
runner_path = ensure_reference_runner(args, functional_model_path, common_dir, steps)
|
||||
raptor_pim_dir = path_from_report(reused, "raptor_pim")
|
||||
raptor_pimsim_dir = path_from_report(reused, "raptor_pimsim_nn")
|
||||
arrays_in_order = load_saved_inputs(
|
||||
@@ -1519,7 +1608,6 @@ def main():
|
||||
print_step("Reuse Raptor")
|
||||
print(f" Report: {reuse_report_path}")
|
||||
|
||||
expected_runner_path = common_dir / "runner/build/runner"
|
||||
common_inputs_dir = common_dir / "inputs"
|
||||
common_reference_dir = common_dir / "outputs"
|
||||
|
||||
@@ -1539,20 +1627,15 @@ def main():
|
||||
else:
|
||||
inputs_ready = False
|
||||
|
||||
if expected_runner_path.exists():
|
||||
runner_path = expected_runner_path
|
||||
else:
|
||||
reference_compile = try_stage(
|
||||
failures,
|
||||
"Compile reference",
|
||||
compile_reference,
|
||||
args,
|
||||
functional_model_path,
|
||||
common_dir,
|
||||
steps,
|
||||
)
|
||||
if reference_compile is not None:
|
||||
runner_path = reference_compile
|
||||
runner_path = try_stage(
|
||||
failures,
|
||||
"Prepare reference runner",
|
||||
ensure_reference_runner,
|
||||
args,
|
||||
functional_model_path,
|
||||
common_dir,
|
||||
steps,
|
||||
)
|
||||
|
||||
if runner_path is not None and runner_path.exists() and model_io is not None:
|
||||
if inputs_ready and reference_outputs_exist(outputs_desc, common_reference_dir):
|
||||
@@ -1570,9 +1653,11 @@ def main():
|
||||
steps,
|
||||
args,
|
||||
common_dir,
|
||||
print_header=not reference_stage_started,
|
||||
)
|
||||
if generated_reference is not None:
|
||||
reference_dir = generated_reference
|
||||
reference_stage_started = True
|
||||
else:
|
||||
record_failure(
|
||||
failures,
|
||||
@@ -1589,28 +1674,32 @@ def main():
|
||||
write_input_batch_csv(out_dir / "inputs.csv", input_batch)
|
||||
raptor_input_bins = write_input_batch_binaries(input_batch, out_dir / "simulation/raptor_inputs")
|
||||
if args.pimsim_mode == "throughput":
|
||||
batch_reference_dir = common_dir / f"reference_batch_{args.batch_size}_seed_{args.seed}"
|
||||
throughput_references = try_stage(
|
||||
failures,
|
||||
"Run throughput references",
|
||||
generate_reference_batch_outputs,
|
||||
"Run reference",
|
||||
prepare_reference_batch_outputs,
|
||||
runner_path,
|
||||
runner_path.parent,
|
||||
functional_model_path,
|
||||
input_batch,
|
||||
outputs_desc,
|
||||
steps,
|
||||
args,
|
||||
out_dir / "reference",
|
||||
batch_reference_dir,
|
||||
print_header=not reference_stage_started,
|
||||
) if runner_path is not None and runner_path.exists() else None
|
||||
if throughput_references is not None:
|
||||
reference_dirs = throughput_references
|
||||
reference_dir = out_dir / "reference"
|
||||
reference_dir = batch_reference_dir
|
||||
reference_stage_started = True
|
||||
elif reference_dir is not None:
|
||||
reference_dirs = [reference_dir]
|
||||
|
||||
if not reuse_raptor and model_path.exists() and hardware["core_count"] > 0:
|
||||
compiled_raptor = try_stage(
|
||||
failures,
|
||||
"Compile Raptor PIM",
|
||||
"Compile Raptor Pim",
|
||||
compile_raptor_target,
|
||||
model_path,
|
||||
out_dir / "raptor",
|
||||
@@ -1623,8 +1712,8 @@ def main():
|
||||
elif not reuse_raptor:
|
||||
record_failure(
|
||||
failures,
|
||||
"Skip Raptor PIM compile",
|
||||
"Raptor PIM compile was skipped because the ONNX model or hardware configuration is not available.",
|
||||
"Skip Raptor Pim compile",
|
||||
"Raptor Pim compile was skipped because the ONNX model or hardware configuration is not available.",
|
||||
)
|
||||
|
||||
raptor_functional_pim_dir = raptor_pim_dir
|
||||
@@ -1635,7 +1724,7 @@ def main():
|
||||
):
|
||||
compiled_functional = try_stage(
|
||||
failures,
|
||||
"Compile Raptor functional PIM",
|
||||
"Compile Raptor functional Pim",
|
||||
compile_raptor_target,
|
||||
functional_model_path,
|
||||
out_dir / "raptor_functional",
|
||||
@@ -1659,9 +1748,9 @@ def main():
|
||||
if wrote_inputs and reference_dirs and outputs_desc:
|
||||
validation = try_stage(
|
||||
failures,
|
||||
"Functional Validation Raptor",
|
||||
"Functional validation Raptor",
|
||||
run_functional_validation,
|
||||
"Functional Validation Raptor",
|
||||
"Functional validation Raptor",
|
||||
raptor_functional_pim_dir,
|
||||
raptor_functional_pim_dir / "config.json",
|
||||
out_dir / "simulation/out.bin",
|
||||
@@ -1679,7 +1768,7 @@ def main():
|
||||
else:
|
||||
raptor_validation = skipped_validation("Raptor input materialization failed")
|
||||
elif not reuse_raptor:
|
||||
raptor_validation = skipped_validation("Raptor PIM compilation did not produce a PIM directory")
|
||||
raptor_validation = skipped_validation("Raptor Pim compilation did not produce a Pim directory")
|
||||
|
||||
pimcomp_model_path = model_path
|
||||
|
||||
@@ -1687,7 +1776,7 @@ def main():
|
||||
reused_pimcomp_dir = args.reuse_pimcomp_dir.resolve()
|
||||
copied_pimcomp = try_stage_success(
|
||||
failures,
|
||||
"Reuse PIMCOMP outputs",
|
||||
"Reuse Pimcomp outputs",
|
||||
copy_pimcomp_outputs,
|
||||
reused_pimcomp_dir,
|
||||
out_dir / "pimcomp/output",
|
||||
@@ -1695,12 +1784,12 @@ def main():
|
||||
if copied_pimcomp:
|
||||
verification_info = out_dir / "pimcomp/output/VerificationInfo.json"
|
||||
simulation_info = out_dir / "pimcomp/output/SimulationInfo.gz"
|
||||
print_step("Reuse PIMCOMP")
|
||||
print_step("Reuse Pimcomp")
|
||||
print(f" Directory: {reused_pimcomp_dir}")
|
||||
elif not reuse_pimcomp:
|
||||
elif run_pimcomp:
|
||||
compiled_pimcomp = try_stage(
|
||||
failures,
|
||||
"Compile PIMCOMP",
|
||||
"Compile Pimcomp",
|
||||
compile_pimcomp,
|
||||
args,
|
||||
pimcomp_model_path,
|
||||
@@ -1710,12 +1799,12 @@ def main():
|
||||
if compiled_pimcomp is not None:
|
||||
verification_info, simulation_info = compiled_pimcomp
|
||||
|
||||
if reuse_pimcomp:
|
||||
if reuse_pimcomp or args.raptor_only:
|
||||
pass
|
||||
elif verification_info is not None and simulation_info is not None and model_io is not None:
|
||||
exported = try_stage(
|
||||
failures,
|
||||
"Export PIMCOMP for Functional Validation",
|
||||
"Export Pimcomp for functional validation",
|
||||
export_pimcomp_for_rust,
|
||||
pimcomp_model_path,
|
||||
verification_info,
|
||||
@@ -1728,14 +1817,14 @@ def main():
|
||||
elif verification_info is None or simulation_info is None:
|
||||
record_failure(
|
||||
failures,
|
||||
"Export PIMCOMP for Functional Validation",
|
||||
"PIMCOMP functional export failed because PIMCOMP did not produce VerificationInfo.json and SimulationInfo.gz.",
|
||||
"Export Pimcomp for functional validation",
|
||||
"Pimcomp functional export failed because Pimcomp did not produce VerificationInfo.json and SimulationInfo.gz.",
|
||||
)
|
||||
else:
|
||||
record_failure(
|
||||
failures,
|
||||
"Export PIMCOMP for Functional Validation",
|
||||
"PIMCOMP functional export failed because model inputs are not available.",
|
||||
"Export Pimcomp for functional validation",
|
||||
"Pimcomp functional export failed because model inputs are not available.",
|
||||
)
|
||||
|
||||
if input_batch is not None and pimcomp_export_dir is not None:
|
||||
@@ -1745,12 +1834,12 @@ def main():
|
||||
transform=flatten_pimcomp_input,
|
||||
)
|
||||
|
||||
if not reuse_pimcomp and pimcomp_export_dir is not None and reference_dirs and outputs_desc:
|
||||
if run_pimcomp and pimcomp_export_dir is not None and reference_dirs and outputs_desc:
|
||||
validation = try_stage(
|
||||
failures,
|
||||
"Functional Validation PIMCOMP",
|
||||
"Functional validation Pimcomp",
|
||||
run_functional_validation,
|
||||
"Functional Validation PIMCOMP",
|
||||
"Functional validation Pimcomp",
|
||||
pimcomp_export_dir,
|
||||
pimcomp_export_dir / "config.json",
|
||||
out_dir / "simulation/pimcomp.out.bin",
|
||||
@@ -1761,11 +1850,11 @@ def main():
|
||||
args,
|
||||
channel_last=True,
|
||||
)
|
||||
pimcomp_validation = validation if validation is not None else failed_validation("PIMCOMP validation failed")
|
||||
elif reuse_pimcomp:
|
||||
pimcomp_validation = validation if validation is not None else failed_validation("Pimcomp validation failed")
|
||||
elif reuse_pimcomp or args.raptor_only:
|
||||
pass
|
||||
elif pimcomp_export_dir is None:
|
||||
pimcomp_validation = failed_validation("PIMCOMP functional export is not available")
|
||||
pimcomp_validation = failed_validation("Pimcomp functional export is not available")
|
||||
elif not reference_dirs:
|
||||
pimcomp_validation = failed_validation("Reference outputs are not available")
|
||||
else:
|
||||
@@ -1774,7 +1863,7 @@ def main():
|
||||
if not args.skip_pimsim_nn and hardware["core_count"] > 0:
|
||||
written_config = try_stage(
|
||||
failures,
|
||||
"Prepare pimsim-nn config",
|
||||
"Prepare Pimsim config",
|
||||
prepare_pimsim_config,
|
||||
args,
|
||||
hardware,
|
||||
@@ -1784,25 +1873,25 @@ def main():
|
||||
elif not args.skip_pimsim_nn:
|
||||
record_failure(
|
||||
failures,
|
||||
"Skip pimsim-nn config",
|
||||
"pimsim-nn config was skipped because the hardware configuration is not available.",
|
||||
"Skip Pimsim config",
|
||||
"Pimsim config was skipped because the hardware configuration is not available.",
|
||||
)
|
||||
|
||||
if args.skip_pimsim_nn:
|
||||
if not reuse_raptor:
|
||||
raptor_perf = skipped_perf("Skipped by --skip-pimsim-nn")
|
||||
if not reuse_pimcomp:
|
||||
if run_pimcomp:
|
||||
pimcomp_perf = skipped_perf("Skipped by --skip-pimsim-nn")
|
||||
elif pimsim_config is None:
|
||||
if not reuse_raptor:
|
||||
raptor_perf = skipped_perf("pimsim-nn config is not available")
|
||||
if not reuse_pimcomp:
|
||||
pimcomp_perf = skipped_perf("pimsim-nn config is not available")
|
||||
raptor_perf = skipped_perf("Pimsim config is not available")
|
||||
if run_pimcomp:
|
||||
pimcomp_perf = skipped_perf("Pimsim config is not available")
|
||||
else:
|
||||
if not reuse_raptor and raptor_pim_dir is not None:
|
||||
raptor_pimsim_dir = try_stage(
|
||||
failures,
|
||||
"Export Raptor for pimsim-nn",
|
||||
"Export Raptor for Pimsim",
|
||||
export_raptor_pimsim_artifact,
|
||||
raptor_pim_dir,
|
||||
out_dir / "raptor/pimsim_nn",
|
||||
@@ -1810,25 +1899,25 @@ def main():
|
||||
if raptor_pimsim_dir is not None:
|
||||
perf = try_stage(
|
||||
failures,
|
||||
"Non-Functional Simulation Raptor",
|
||||
"Non-functional simulation Raptor",
|
||||
run_pimsim_nn,
|
||||
"Non-Functional Simulation Raptor",
|
||||
"Non-functional simulation Raptor",
|
||||
raptor_pimsim_dir,
|
||||
pimsim_config,
|
||||
steps,
|
||||
args,
|
||||
)
|
||||
raptor_perf = perf if perf is not None else failed_perf("pimsim-nn Raptor failed")
|
||||
raptor_perf = perf if perf is not None else failed_perf("Pimsim Raptor failed")
|
||||
else:
|
||||
raptor_perf = failed_perf("Raptor pimsim-nn export failed")
|
||||
raptor_perf = failed_perf("Raptor Pimsim export failed")
|
||||
elif not reuse_raptor:
|
||||
raptor_perf = skipped_perf("Raptor PIM directory is not available")
|
||||
raptor_perf = skipped_perf("Raptor Pim directory is not available")
|
||||
|
||||
if not reuse_pimcomp:
|
||||
if run_pimcomp:
|
||||
if simulation_info is not None:
|
||||
pimcomp_pimsim_dir = try_stage(
|
||||
failures,
|
||||
"Export PIMCOMP for pimsim-nn",
|
||||
"Export Pimcomp for Pimsim",
|
||||
export_pimcomp_for_pimsim_nn,
|
||||
simulation_info,
|
||||
out_dir / "pimcomp/pimsim_nn",
|
||||
@@ -1836,32 +1925,32 @@ def main():
|
||||
if pimcomp_pimsim_dir is not None:
|
||||
perf = try_stage(
|
||||
failures,
|
||||
"Non-Functional Simulation PIMCOMP",
|
||||
"Non-functional simulation Pimcomp",
|
||||
run_pimsim_nn,
|
||||
"Non-Functional Simulation PIMCOMP",
|
||||
"Non-functional simulation Pimcomp",
|
||||
pimcomp_pimsim_dir,
|
||||
pimsim_config,
|
||||
steps,
|
||||
args,
|
||||
)
|
||||
pimcomp_perf = perf if perf is not None else failed_perf("pimsim-nn PIMCOMP failed")
|
||||
pimcomp_perf = perf if perf is not None else failed_perf("Pimsim Pimcomp failed")
|
||||
else:
|
||||
pimcomp_perf = failed_perf("PIMCOMP pimsim-nn export failed")
|
||||
pimcomp_perf = failed_perf("Pimcomp Pimsim export failed")
|
||||
else:
|
||||
pimcomp_perf = skipped_perf("PIMCOMP SimulationInfo.gz is not available")
|
||||
pimcomp_perf = skipped_perf("Pimcomp SimulationInfo.gz is not available")
|
||||
|
||||
if not reuse_raptor and raptor_pim_dir is not None and raptor_pim_dir.exists():
|
||||
parsed = try_stage(failures, "Parse Raptor instructions", parse_raptor_instructions, raptor_pim_dir)
|
||||
raptor_instr = parsed if parsed is not None else empty_instruction_summary(error="Failed to parse Raptor instructions")
|
||||
elif not reuse_raptor:
|
||||
raptor_instr = empty_instruction_summary("Raptor PIM directory is not available")
|
||||
raptor_instr = empty_instruction_summary("Raptor Pim directory is not available")
|
||||
|
||||
if not reuse_pimcomp:
|
||||
if run_pimcomp:
|
||||
if simulation_info is not None and simulation_info.exists():
|
||||
parsed = try_stage(failures, "Parse PIMCOMP instructions", parse_pimcomp_instructions, simulation_info)
|
||||
pimcomp_instr = parsed if parsed is not None else empty_instruction_summary(error="Failed to parse PIMCOMP instructions")
|
||||
parsed = try_stage(failures, "Parse Pimcomp instructions", parse_pimcomp_instructions, simulation_info)
|
||||
pimcomp_instr = parsed if parsed is not None else empty_instruction_summary(error="Failed to parse Pimcomp instructions")
|
||||
else:
|
||||
pimcomp_instr = empty_instruction_summary("PIMCOMP SimulationInfo.gz is not available")
|
||||
pimcomp_instr = empty_instruction_summary("Pimcomp SimulationInfo.gz is not available")
|
||||
|
||||
report_path = out_dir / "pimcomp/comparison_report.md"
|
||||
write_report(
|
||||
@@ -1890,7 +1979,7 @@ def main():
|
||||
"pimsim_time_ms": args.pimsim_time_ms,
|
||||
"pimcomp_pipeline": args.pimcomp_pipeline,
|
||||
"pimcomp_replication": args.pimcomp_replication,
|
||||
"pimcomp_model_source": "original_onnx",
|
||||
"pimcomp_model_source": "not_run" if args.raptor_only else "original_onnx",
|
||||
"pimcomp_config": str(args.pimcomp_config),
|
||||
"raptor_extra_args": args.raptor_extra_arg,
|
||||
"reused_raptor_report": optional_path(args.reuse_raptor_report.resolve()) if reuse_raptor else None,
|
||||
@@ -1926,7 +2015,11 @@ def main():
|
||||
json.dump(json_report, f, indent=2)
|
||||
f.write("\n")
|
||||
|
||||
partial_side = "pimcomp" if reuse_raptor else "raptor" if args.reuse_pimcomp_report else None
|
||||
partial_side = (
|
||||
"pimcomp" if reuse_raptor
|
||||
else "raptor" if args.raptor_only or args.reuse_pimcomp_report
|
||||
else None
|
||||
)
|
||||
other_side = "RAPTOR" if partial_side == "pimcomp" else "PIMCOMP"
|
||||
relevant_failures = (
|
||||
failures if partial_side is None
|
||||
@@ -1947,7 +2040,7 @@ def main():
|
||||
for result in relevant_validations
|
||||
)
|
||||
failed = bool(relevant_failures or failed_steps or functional_failure)
|
||||
print("\n" + Style.BRIGHT + Fore.GREEN + "[Completed]" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + Fore.GREEN + "[Completed]" + Style.RESET_ALL)
|
||||
print(f" Report: {report_path}")
|
||||
print(f" JSON: {json_path}")
|
||||
if failures or failed_steps:
|
||||
+192
-105
@@ -19,33 +19,30 @@ from colorama import Fore, Style
|
||||
|
||||
|
||||
REPO = Path(__file__).resolve().parents[5]
|
||||
SUITE = REPO / "validation/networks/pimcomp_models"
|
||||
sys.path.insert(0, str(REPO / "validation"))
|
||||
|
||||
from raptor_validation.pimsim_nn import parse_pimsim_nn_metrics # noqa: E402
|
||||
from raptor_validation.validate_one import STAGE_COLORS # noqa: E402
|
||||
from raptor_validation.pimcomp_models import (
|
||||
FUNCTIONAL_MODELS,
|
||||
MODELS,
|
||||
SUITE,
|
||||
add_models_argument,
|
||||
)
|
||||
from raptor_validation.artifacts import artifacts_dir, remove_lock_files
|
||||
from raptor_validation.pimsim_nn import parse_pimsim_nn_metrics
|
||||
from raptor_validation.validate_one import STAGE_COLORS
|
||||
|
||||
PIMCOMP_SOURCE = REPO / "third_party/PIMCOMP-NN"
|
||||
PIMCOMP_CONFIGS = REPO / "validation/pimsim_configs/pimcomp"
|
||||
COMPARE = Path(__file__).resolve().with_name("compare_raptor_pimcomp.py")
|
||||
COMPARE = Path(__file__).resolve().with_name("compare_raptor_pimcomp_model.py")
|
||||
ARCHES = tuple(sorted(path.name for path in PIMCOMP_CONFIGS.iterdir() if path.is_dir()))
|
||||
MODELS = {
|
||||
"vgg8": SUITE / "vgg8/vgg8-mnist-reconstructed.onnx",
|
||||
"resnet18": SUITE / "resnet18/resnet18-v1-7.onnx",
|
||||
"resnet34": SUITE / "resnet34/resnet34-v1-7.onnx",
|
||||
"googlenet": SUITE / "googlenet/googlenet-12-pimsim-nn.onnx",
|
||||
"yolo11n": SUITE / "yolo11n/yolo11n-pimsim-nn.onnx",
|
||||
}
|
||||
FUNCTIONAL_MODELS = {
|
||||
**MODELS,
|
||||
"yolo11n": REPO / "validation/networks/yolo11n/depth_51/yolo11n_depth_51.onnx",
|
||||
}
|
||||
DEFAULT_ARCHES = ("arch-a", "arch-b")
|
||||
COMPARISONS = (
|
||||
("latency", 1, "element"),
|
||||
("throughput", 2, "batch"),
|
||||
("throughput", 4, "batch"),
|
||||
("throughput", 8, "batch"),
|
||||
)
|
||||
RESULTS_FILENAME = "results_comparison.csv"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -67,39 +64,60 @@ def model_dir(root: Path | None, name: str) -> Path:
|
||||
return root / name if root is not None else MODELS[name].parent
|
||||
|
||||
|
||||
def result_dir(root: Path | None, name: str, arch: str, mode: str, pipeline: int) -> Path:
|
||||
base = model_dir(root, name)
|
||||
def result_dir(
|
||||
root: Path | None,
|
||||
name: str,
|
||||
arch: str,
|
||||
mode: str,
|
||||
pipeline: int,
|
||||
ablation_variant: str | None = None,
|
||||
) -> Path:
|
||||
base = artifacts_dir(model_dir(root, name))
|
||||
suffix = "latency" if mode == "latency" else f"throughput/pipeline{pipeline}"
|
||||
return base / arch / suffix
|
||||
result = base / arch / suffix
|
||||
if ablation_variant is not None:
|
||||
result /= Path("ablation") / ablation_variant
|
||||
return result
|
||||
|
||||
|
||||
def common_dir(root: Path | None, name: str) -> Path:
|
||||
def common_dir(root: Path | None, name: str, common_root: Path | None = None) -> Path:
|
||||
suffix = "common" if FUNCTIONAL_MODELS[name] == MODELS[name] else "common-functional"
|
||||
return model_dir(root, name) / suffix
|
||||
base = common_root / name if common_root is not None else model_dir(root, name)
|
||||
return artifacts_dir(base) / suffix
|
||||
|
||||
|
||||
def clean_artifacts(root: Path | None, models: list[str], arches: list[str]) -> int:
|
||||
def clean_artifacts(
|
||||
root: Path | None,
|
||||
models: list[str],
|
||||
common_root: Path | None = None,
|
||||
) -> int:
|
||||
removed = 0
|
||||
for name in models:
|
||||
base = model_dir(root, name)
|
||||
arch_dirs = {base / arch for arch in arches}
|
||||
arch_dirs.update(path for path in base.glob("arch-*") if path.is_dir() and not path.is_symlink())
|
||||
for path in arch_dirs:
|
||||
for path in (artifacts_dir(base),):
|
||||
if path.is_dir() and not path.is_symlink():
|
||||
shutil.rmtree(path)
|
||||
removed += 1
|
||||
for common in (base / "common", base / "common-functional"):
|
||||
if common.is_dir() and not common.is_symlink():
|
||||
shutil.rmtree(common)
|
||||
removed += 1
|
||||
for path in (
|
||||
(root or SUITE) / "results.csv",
|
||||
(root or SUITE) / "results_latency.csv",
|
||||
(root or SUITE) / "results_throughput.csv",
|
||||
(root or SUITE) / RESULTS_FILENAME,
|
||||
(root or SUITE) / "results_ablation.csv",
|
||||
):
|
||||
if path.is_file() or path.is_symlink():
|
||||
path.unlink()
|
||||
removed += 1
|
||||
summary_root = root or SUITE
|
||||
if (summary_root / "ablation").is_dir():
|
||||
for path in (summary_root / "ablation").glob("*/results_comparison.csv"):
|
||||
path.unlink(missing_ok=True)
|
||||
removed += 1
|
||||
removed += remove_lock_files(summary_root)
|
||||
if common_root is not None:
|
||||
for name in models:
|
||||
path = artifacts_dir(common_root / name)
|
||||
if path.is_dir() and not path.is_symlink():
|
||||
shutil.rmtree(path)
|
||||
removed += 1
|
||||
removed += remove_lock_files(common_root)
|
||||
return removed
|
||||
|
||||
|
||||
@@ -108,8 +126,11 @@ def write_results_csv(
|
||||
arch: str,
|
||||
models: list[str],
|
||||
comparisons: tuple[tuple[str, int, str], ...] = COMPARISONS,
|
||||
ablation_variant: str | None = None,
|
||||
) -> Path:
|
||||
output = (root or SUITE) / "results.csv"
|
||||
output = (root or SUITE) / RESULTS_FILENAME
|
||||
if ablation_variant is not None:
|
||||
output = output.parent / "ablation" / ablation_variant / RESULTS_FILENAME
|
||||
fields = (
|
||||
"arch",
|
||||
"model",
|
||||
@@ -152,7 +173,9 @@ def write_results_csv(
|
||||
rows.append({field: row.get(field, "NA") for field in fields})
|
||||
for name in models:
|
||||
for comparison_mode, pipeline, pimcomp_pipeline in comparisons:
|
||||
report_path = result_dir(root, name, arch, comparison_mode, pipeline) / "pimcomp/comparison_report.json"
|
||||
report_path = result_dir(
|
||||
root, name, arch, comparison_mode, pipeline, ablation_variant
|
||||
) / "pimcomp/comparison_report.json"
|
||||
row = {
|
||||
"model": name,
|
||||
"arch": arch,
|
||||
@@ -215,6 +238,7 @@ def write_results_csv(
|
||||
int(row["raptor_pipeline"]),
|
||||
)
|
||||
)
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=fields, lineterminator="\n")
|
||||
writer.writeheader()
|
||||
@@ -275,11 +299,11 @@ def format_value(value: float | None) -> str:
|
||||
|
||||
|
||||
def print_stage(title: str, color: str) -> None:
|
||||
print("\n" + Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL, flush=True)
|
||||
print(Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL, flush=True)
|
||||
|
||||
|
||||
def print_completed(label: str, output: str = "") -> None:
|
||||
print("\n" + Style.BRIGHT + Fore.CYAN + f"[Completed {label}]" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + Fore.CYAN + f"[Completed {label}]" + Style.RESET_ALL)
|
||||
if output:
|
||||
print(output, end="" if output.endswith("\n") else "\n")
|
||||
print("=" * 72, flush=True)
|
||||
@@ -321,7 +345,7 @@ def validate_pimcomp_source() -> None:
|
||||
source = header.read_text(encoding="utf-8")
|
||||
for setting in ("int population_num = 200;", "int max_iteration = 1000;"):
|
||||
if setting not in source:
|
||||
raise RuntimeError(f"PIMCOMP paper setting is missing: {setting}")
|
||||
raise RuntimeError(f"Pimcomp paper setting is missing: {setting}")
|
||||
|
||||
|
||||
def comparison_command(
|
||||
@@ -337,6 +361,8 @@ def comparison_command(
|
||||
batch_size: int,
|
||||
timeout: float,
|
||||
fast: bool,
|
||||
raptor_extra_args: list[str] | tuple[str, ...] = (),
|
||||
raptor_only: bool = False,
|
||||
reuse_raptor_report: Path | None = None,
|
||||
reuse_pimcomp_dir: Path | None = None,
|
||||
reuse_pimcomp_report: Path | None = None,
|
||||
@@ -373,7 +399,9 @@ def comparison_command(
|
||||
pimcomp_pipeline,
|
||||
"--pimcomp-replication",
|
||||
"GA",
|
||||
*(["--raptor-only"] if raptor_only else []),
|
||||
f"--raptor-extra-arg=--pipeline={pipeline}",
|
||||
*[f"--raptor-extra-arg={arg}" for arg in raptor_extra_args],
|
||||
"--timeout-seconds",
|
||||
str(timeout),
|
||||
"--fail-on-error",
|
||||
@@ -418,13 +446,19 @@ def comparison_command_for(
|
||||
args.batch_size,
|
||||
args.timeout_seconds,
|
||||
not args.no_fast,
|
||||
args.raptor_extra_args,
|
||||
args.raptor_only,
|
||||
reuse_raptor_report=(
|
||||
report
|
||||
if args.only == "pimcomp"
|
||||
else None
|
||||
),
|
||||
reuse_pimcomp_dir=spec.shared_pimcomp_dir if reuse_shared_pimcomp and args.only != "raptor" else None,
|
||||
reuse_pimcomp_report=report if args.only == "raptor" else None,
|
||||
reuse_pimcomp_dir=(
|
||||
spec.shared_pimcomp_dir
|
||||
if reuse_shared_pimcomp and args.only != "raptor" and not args.raptor_only
|
||||
else None
|
||||
),
|
||||
reuse_pimcomp_report=report if args.only == "raptor" and not args.raptor_only else None,
|
||||
)
|
||||
|
||||
|
||||
@@ -497,20 +531,29 @@ def config_path(arch: str, mode: str, sim_time_ms: int, *, write: bool) -> Path:
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Compare supported PIMCOMP models with Raptor latency and throughput schedules."
|
||||
description="Compare supported Pimcomp models with Raptor latency and throughput schedules."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--out-dir",
|
||||
type=Path,
|
||||
help="Result root (default: artifacts beside each model under validation/).",
|
||||
help="Suite root; generated artifacts go below each model's artifacts/ directory (default: validation/networks/pimcomp_models).",
|
||||
)
|
||||
parser.add_argument("--models", nargs="+", choices=MODELS, default=list(MODELS))
|
||||
parser.add_argument(
|
||||
"--arch",
|
||||
"--common-dir",
|
||||
type=Path,
|
||||
help="Shared root for per-model reference artifacts. Default: inside --out-dir.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ablation-variant",
|
||||
help="Place Raptor artifacts below <mode>/ablation/<variant> and write the comparison summary below ablation/<variant>.",
|
||||
)
|
||||
add_models_argument(parser)
|
||||
parser.add_argument(
|
||||
"--archs",
|
||||
nargs="+",
|
||||
choices=ARCHES,
|
||||
default=list(ARCHES),
|
||||
help="PIM architectures to run (default: all architectures).",
|
||||
default=list(DEFAULT_ARCHES),
|
||||
help=f"Pim architectures to run (default: {', '.join(DEFAULT_ARCHES)}).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
@@ -524,6 +567,11 @@ def main() -> int:
|
||||
choices=("raptor", "pimcomp"),
|
||||
help="Re-run only this compiler's compile, validation, and simulation stages; preserve the other side from its report.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--raptor-only",
|
||||
action="store_true",
|
||||
help="Run only Raptor; do not compile, validate, or simulate Pimcomp. Default: off.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pipeline",
|
||||
type=int,
|
||||
@@ -534,7 +582,7 @@ def main() -> int:
|
||||
"--pimsim-time-ms",
|
||||
type=int,
|
||||
default=1000,
|
||||
help="throughput pimsim-nn convergence deadline in ms (default: 1000).",
|
||||
help="Throughput Pimsim convergence deadline in ms (default: 1000).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--batch-size",
|
||||
@@ -564,15 +612,32 @@ def main() -> int:
|
||||
parser.add_argument(
|
||||
"--no-fast",
|
||||
action="store_true",
|
||||
help="Disable fast pimsim-nn throughput convergence for authoritative experiments.",
|
||||
help="Disable fast Pimsim throughput convergence for authoritative experiments.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--raptor-extra-arg",
|
||||
action="append",
|
||||
default=[],
|
||||
dest="raptor_extra_args",
|
||||
help="Additional argument to pass to Raptor; repeat as needed.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.ablation_variant and Path(args.ablation_variant).name != args.ablation_variant:
|
||||
parser.error("--ablation-variant must be a single directory name")
|
||||
|
||||
if args.only is not None and args.raptor_only:
|
||||
parser.error("--only cannot be combined with --raptor-only")
|
||||
|
||||
out_dir = args.out_dir.resolve() if args.out_dir is not None else None
|
||||
args.arch = list(dict.fromkeys(args.arch))
|
||||
common_root = args.common_dir.resolve() if args.common_dir is not None else None
|
||||
args.archs = list(dict.fromkeys(args.archs))
|
||||
args.mode = list(dict.fromkeys(args.mode))
|
||||
if args.clean:
|
||||
print(f"Removed {clean_artifacts(out_dir, args.models, args.arch)} comparison artifact path(s).")
|
||||
print(
|
||||
f"Removed {clean_artifacts(out_dir, args.models, common_root)} "
|
||||
"comparison artifact path(s)."
|
||||
)
|
||||
return 0
|
||||
|
||||
if args.jobs < 1:
|
||||
@@ -585,7 +650,7 @@ def main() -> int:
|
||||
parser.error("--timeout-seconds must be non-negative")
|
||||
comparisons_by_arch: dict[str, tuple[tuple[str, int, str], ...]] = {}
|
||||
configs_by_arch: dict[str, dict[str, Path]] = {}
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
comparisons = tuple(
|
||||
comparison for comparison in COMPARISONS
|
||||
if comparison[0] in args.mode
|
||||
@@ -611,10 +676,12 @@ def main() -> int:
|
||||
|
||||
if args.only is not None:
|
||||
missing_reuse = []
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
for name in args.models:
|
||||
for mode, pipeline, _ in comparisons_by_arch[arch]:
|
||||
comparison_dir = result_dir(out_dir, name, arch, mode, pipeline)
|
||||
comparison_dir = result_dir(
|
||||
out_dir, name, arch, mode, pipeline, args.ablation_variant
|
||||
)
|
||||
required = comparison_dir / "pimcomp/comparison_report.json"
|
||||
if not required.exists():
|
||||
missing_reuse.append(str(required))
|
||||
@@ -629,14 +696,15 @@ def main() -> int:
|
||||
+ ", ".join(missing_reuse)
|
||||
)
|
||||
|
||||
validate_pimcomp_source()
|
||||
if not args.raptor_only:
|
||||
validate_pimcomp_source()
|
||||
if out_dir is not None and not args.dry_run:
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
print(Style.BRIGHT + f"Found {len(args.models)} PIMCOMP model(s) to compare." + Style.RESET_ALL)
|
||||
print(f"Architectures: {', '.join(args.arch)}")
|
||||
print(Style.BRIGHT + f"Found {len(args.models)} Pimcomp model(s) to compare." + Style.RESET_ALL)
|
||||
print(f"Architectures: {', '.join(args.archs)}")
|
||||
print(f"Modes: {', '.join(args.mode)}")
|
||||
print(f"Throughput pimsim time: {args.pimsim_time_ms} ms")
|
||||
print(f"Throughput Pimsim time: {args.pimsim_time_ms} ms")
|
||||
print(f"Max parallel jobs: {args.jobs}")
|
||||
print(
|
||||
f"Comparison jobs: "
|
||||
@@ -645,29 +713,36 @@ def main() -> int:
|
||||
print(f"Results root: {out_dir or SUITE}")
|
||||
print("=" * 72)
|
||||
|
||||
print_stage("Prepare shared artifacts", STAGE_COLORS["Build Runner"])
|
||||
for name in args.models:
|
||||
try:
|
||||
run(
|
||||
prepare_common_command(
|
||||
FUNCTIONAL_MODELS[name],
|
||||
common_dir(out_dir, name),
|
||||
args.timeout_seconds,
|
||||
),
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
finally:
|
||||
print_completed(f"shared artifacts: {name}")
|
||||
print_stage("Prepare shared artifacts", STAGE_COLORS["Build runner"])
|
||||
try:
|
||||
for name in args.models:
|
||||
try:
|
||||
run(
|
||||
prepare_common_command(
|
||||
FUNCTIONAL_MODELS[name],
|
||||
common_dir(out_dir, name, common_root),
|
||||
args.timeout_seconds,
|
||||
),
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
finally:
|
||||
print_completed(f"shared artifacts: {name}")
|
||||
except KeyboardInterrupt:
|
||||
remove_lock_files(out_dir or SUITE)
|
||||
print("Interrupted; cleaned validation lock files.", file=sys.stderr)
|
||||
return 130
|
||||
|
||||
failed = []
|
||||
comparison_specs: list[ComparisonSpec] = []
|
||||
shared_pimcomp_by_group: dict[tuple[str, str, str], Path] = {}
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
comparisons = comparisons_by_arch[arch]
|
||||
configs = configs_by_arch[arch]
|
||||
for name in args.models:
|
||||
for mode, pipeline, pimcomp_pipeline in comparisons:
|
||||
model_result_dir = result_dir(out_dir, name, arch, mode, pipeline)
|
||||
model_result_dir = result_dir(
|
||||
out_dir, name, arch, mode, pipeline, args.ablation_variant
|
||||
)
|
||||
label = f"{arch}/{name}/{mode}/pipeline{pipeline}"
|
||||
group = (arch, name, mode)
|
||||
shared_pimcomp_dir = shared_pimcomp_by_group.get(group)
|
||||
@@ -681,7 +756,7 @@ def main() -> int:
|
||||
model=MODELS[name],
|
||||
functional_model=FUNCTIONAL_MODELS[name],
|
||||
output_dir=model_result_dir,
|
||||
common_dir=common_dir(out_dir, name),
|
||||
common_dir=common_dir(out_dir, name, common_root),
|
||||
config=configs[mode],
|
||||
mode=mode,
|
||||
pipeline=pipeline,
|
||||
@@ -701,7 +776,8 @@ def main() -> int:
|
||||
if run(command, dry_run=True, check=False):
|
||||
failed.append(spec.label)
|
||||
elif comparison_specs:
|
||||
anchor_specs = comparison_specs if args.only == "raptor" else [
|
||||
raptor_only_run = args.only == "raptor" or args.raptor_only
|
||||
anchor_specs = comparison_specs if raptor_only_run else [
|
||||
spec for spec in comparison_specs if spec.anchor
|
||||
]
|
||||
anchor_jobs = [
|
||||
@@ -712,64 +788,75 @@ def main() -> int:
|
||||
)
|
||||
for spec in anchor_specs
|
||||
]
|
||||
dependent_specs = [] if args.only == "raptor" else [
|
||||
dependent_specs = [] if raptor_only_run else [
|
||||
spec for spec in comparison_specs if not spec.anchor
|
||||
]
|
||||
print_directly = min(args.jobs, len(comparison_specs)) == 1
|
||||
with (nullcontext(None) if print_directly else TemporaryDirectory(prefix="raptor-pimcomp-")) as log_dir:
|
||||
anchor_failed, log_offset = run_comparison_jobs(
|
||||
anchor_jobs,
|
||||
args.jobs,
|
||||
Path(log_dir) if log_dir else None,
|
||||
0,
|
||||
)
|
||||
failed.extend(anchor_failed)
|
||||
dependent_jobs = [
|
||||
(
|
||||
spec.label,
|
||||
comparison_command_for(
|
||||
spec,
|
||||
args,
|
||||
reuse_shared_pimcomp=pimcomp_artifact_ready(spec.shared_pimcomp_dir),
|
||||
),
|
||||
None,
|
||||
try:
|
||||
with (nullcontext(None) if print_directly else TemporaryDirectory(prefix="raptor-pimcomp-")) as log_dir:
|
||||
anchor_failed, log_offset = run_comparison_jobs(
|
||||
anchor_jobs,
|
||||
args.jobs,
|
||||
Path(log_dir) if log_dir else None,
|
||||
0,
|
||||
)
|
||||
for spec in dependent_specs
|
||||
]
|
||||
dependent_failed, _ = run_comparison_jobs(
|
||||
dependent_jobs,
|
||||
args.jobs,
|
||||
Path(log_dir) if log_dir else None,
|
||||
log_offset,
|
||||
)
|
||||
failed.extend(dependent_failed)
|
||||
failed.extend(anchor_failed)
|
||||
dependent_jobs = [
|
||||
(
|
||||
spec.label,
|
||||
comparison_command_for(
|
||||
spec,
|
||||
args,
|
||||
reuse_shared_pimcomp=pimcomp_artifact_ready(spec.shared_pimcomp_dir),
|
||||
),
|
||||
None,
|
||||
)
|
||||
for spec in dependent_specs
|
||||
]
|
||||
dependent_failed, _ = run_comparison_jobs(
|
||||
dependent_jobs,
|
||||
args.jobs,
|
||||
Path(log_dir) if log_dir else None,
|
||||
log_offset,
|
||||
)
|
||||
failed.extend(dependent_failed)
|
||||
except KeyboardInterrupt:
|
||||
remove_lock_files(out_dir or SUITE)
|
||||
print("Interrupted; cleaned validation lock files.", file=sys.stderr)
|
||||
return 130
|
||||
|
||||
if args.dry_run:
|
||||
return 1 if failed else 0
|
||||
|
||||
remove_lock_files(out_dir or SUITE)
|
||||
|
||||
results_path = None
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
results_path = write_results_csv(
|
||||
out_dir,
|
||||
arch,
|
||||
args.models,
|
||||
comparisons_by_arch[arch],
|
||||
args.ablation_variant,
|
||||
)
|
||||
assert results_path is not None
|
||||
print_stage(results_path.name, STAGE_COLORS["Compare Outputs"])
|
||||
print_stage(results_path.name, STAGE_COLORS["Compare outputs"])
|
||||
print(results_path.read_text(encoding="utf-8"), end="")
|
||||
for arch in args.arch:
|
||||
for arch in args.archs:
|
||||
for name in args.models:
|
||||
for mode, pipeline, _ in comparisons_by_arch[arch]:
|
||||
label = f"{arch}/{name}/{mode}/pipeline{pipeline}"
|
||||
report_path = result_dir(out_dir, name, arch, mode, pipeline) / "pimcomp/comparison_report.json"
|
||||
report_path = result_dir(
|
||||
out_dir, name, arch, mode, pipeline, args.ablation_variant
|
||||
) / "pimcomp/comparison_report.json"
|
||||
compiler = "raptor" if args.raptor_only else args.only
|
||||
if not report_path.exists() or not comparison_passed(
|
||||
json.loads(report_path.read_text(encoding="utf-8")),
|
||||
args.only,
|
||||
compiler,
|
||||
):
|
||||
if label not in failed:
|
||||
failed.append(label)
|
||||
print("\n" + Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + Fore.CYAN + "Summary" + Style.RESET_ALL)
|
||||
total_jobs = sum(len(args.models) * len(comparisons) for comparisons in comparisons_by_arch.values())
|
||||
print(Style.BRIGHT + f"Passed: {total_jobs - len(failed)}" + Style.RESET_ALL)
|
||||
print(Style.BRIGHT + f"Failed: {len(failed)}" + Style.RESET_ALL)
|
||||
@@ -1,6 +1,6 @@
|
||||
# PIMCOMP batch correctness reproduction
|
||||
# Pimcomp batch correctness reproduction
|
||||
|
||||
PIMCOMP's batch scheduler currently emits an incomplete standalone program for
|
||||
Pimcomp's batch scheduler currently emits an incomplete standalone program for
|
||||
models containing post operations. The generated `VerificationInfo.json` uses a
|
||||
negative `source_address` to identify the preceding node, but the batch
|
||||
verifier resolves that address by copying the provider tensor directly from
|
||||
@@ -17,8 +17,8 @@ the missing computation visible in Rust functional validation.
|
||||
For the checked-in GoogLeNet throughput/pipeline2 artifact, 39 provider tensors
|
||||
are referenced by batch loads. Nineteen have generated stores; twenty are
|
||||
never written. Preloading the provider tensors with the same ONNX Runtime
|
||||
intermediates used by PIMCOMP's verifier makes the exported program pass. This
|
||||
reproduces the verifier's input contract; it does not repair PIMCOMP's batch
|
||||
intermediates used by Pimcomp's verifier makes the exported program pass. This
|
||||
reproduces the verifier's input contract; it does not repair Pimcomp's batch
|
||||
schedule.
|
||||
|
||||
Run the default reproduction from the repository root:
|
||||
@@ -27,23 +27,59 @@ Run the default reproduction from the repository root:
|
||||
.venv/bin/python validation/tools/pim/pimcomp/correctness/run_prefill_experiment.py
|
||||
```
|
||||
|
||||
The launcher accepts an alternate comparison directory, model, and work
|
||||
work directory, and shared reference-artifact directory:
|
||||
The launcher accepts an alternate comparison directory, model, work directory,
|
||||
and shared reference-artifact directory:
|
||||
|
||||
```bash
|
||||
.venv/bin/python validation/tools/pim/pimcomp/correctness/run_prefill_experiment.py \
|
||||
validation/networks/pimcomp_models/googlenet/arch-a/throughput/pipeline2 \
|
||||
validation/networks/pimcomp_models/googlenet/artifacts/arch-a/throughput/pipeline2 \
|
||||
validation/networks/pimcomp_models/googlenet/googlenet-12-pimsim-nn.onnx \
|
||||
/tmp/pimcomp-prefill-googlenet \
|
||||
validation/networks/pimcomp_models/googlenet/common
|
||||
validation/networks/pimcomp_models/googlenet/artifacts/arch-a/throughput/pipeline2/correctness/prefill \
|
||||
validation/networks/pimcomp_models/googlenet/artifacts/common
|
||||
```
|
||||
|
||||
Without the optional work-directory argument, the experiment uses the same
|
||||
`correctness/prefill/` directory below the comparison artifacts.
|
||||
|
||||
It runs the exported artifact once with its original memory image and once
|
||||
with [`prefill_batch_memory.py`](prefill_batch_memory.py), then compares both
|
||||
outputs with the recorded native reference. The expected GoogLeNet result is a
|
||||
baseline maximum difference near `6.70705` and a prefilled maximum difference
|
||||
near `4.05e-6`.
|
||||
|
||||
The issue is in PIMCOMP batch scheduling/validation semantics, not in the Rust
|
||||
The issue is in Pimcomp batch scheduling/validation semantics, not in the Rust
|
||||
simulator's vector-length interpretation. Vector lengths remain element counts
|
||||
as specified by the reference ISA.
|
||||
|
||||
## ResNet BatchNorm correctness gap
|
||||
|
||||
Pimcomp has a separate correctness limitation in its ResNet element pipeline.
|
||||
`BatchNormalization` is an ONNX operation. The frontend's
|
||||
[`fuse_operators()` pass](../../../../../third_party/PIMCOMP-NN/frontend/frontend.py#L535)
|
||||
marks it as fused and removes the node from the
|
||||
scheduled graph, but the released path does not fold the BatchNorm affine
|
||||
transform into the convolution weights and bias. The corresponding verifier
|
||||
workaround in [`verification.py`](../../../../../third_party/PIMCOMP-NN/verification/verification.py#L99)
|
||||
replaces BatchNorm parameters with identity values (scale and variance equal
|
||||
to one, bias and mean equal to zero) before running ONNX Runtime.
|
||||
|
||||
Therefore Pimcomp's native verifier and exported element program agree with
|
||||
each other, but they do not implement the original ResNet ONNX model. On the
|
||||
current ResNet-18 Arch-A latency artifact, using the same input:
|
||||
|
||||
| Comparison | Maximum absolute difference |
|
||||
|---|---:|
|
||||
| Pimcomp native verifier vs Rust export | `6.7e-8` |
|
||||
| Rust export vs original ONNX reference | `4.8294563` |
|
||||
|
||||
The Pimcomp output ranges from approximately `-0.187` to `0.220`, while the
|
||||
original ONNX output ranges from `-3.572` to `4.834`; 462 of 1000 final
|
||||
elements differ by more than one. This is not a Rust simulator or Python
|
||||
exporter regression. It is a Pimcomp model-semantics mismatch caused by
|
||||
dropping BatchNorm numerics. The same issue affects the ResNet-34 latency
|
||||
artifact. VGG and GoogLeNet do not show this particular mismatch because they
|
||||
do not contain the same ResNet BatchNorm path.
|
||||
|
||||
The latency comparison intentionally uses the original ONNX reference, so
|
||||
these Pimcomp rows must remain `FAIL` until Pimcomp folds BatchNorm correctly
|
||||
or the comparison explicitly uses a BatchNorm-neutralized reference.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Populate the host-side intermediate buffers expected by PIMCOMP batch mode."""
|
||||
"""Populate the host-side intermediate buffers expected by Pimcomp batch mode."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -23,7 +23,7 @@ def flatten_reference(value: np.ndarray) -> np.ndarray:
|
||||
elif value.ndim == 2:
|
||||
value = value.transpose()
|
||||
else:
|
||||
raise ValueError(f"PIMCOMP batch verification only flattens 2D/4D tensors, got {value.shape}")
|
||||
raise ValueError(f"Pimcomp batch verification only flattens 2D/4D tensors, got {value.shape}")
|
||||
return value.astype(np.float32, copy=False).reshape(-1)
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ def prefill_batch_memory(
|
||||
session = ort.InferenceSession(runtime_model.SerializeToString(), providers=["CPUExecutionProvider"])
|
||||
session_inputs = session.get_inputs()
|
||||
if len(session_inputs) != 1:
|
||||
raise ValueError("PIMCOMP export currently requires exactly one runtime input tensor")
|
||||
raise ValueError("Pimcomp export currently requires exactly one runtime input tensor")
|
||||
input_meta = session_inputs[0]
|
||||
input_tensor = np.loadtxt(input_path, delimiter=",", dtype=np.float32).reshape(input_meta.shape)
|
||||
provider_names = [node_list[index]["name"] for index in providers]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reproduce the PIMCOMP batch prefill correctness experiment."""
|
||||
"""Reproduce the Pimcomp batch prefill correctness experiment."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -7,7 +7,6 @@ import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
@@ -17,9 +16,9 @@ from prefill_batch_memory import prefill_batch_memory
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
REPO_ROOT = SCRIPT_DIR.parents[4]
|
||||
DEFAULT_COMPARISON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/arch-a/throughput/pipeline2"
|
||||
DEFAULT_COMPARISON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/artifacts/arch-a/throughput/pipeline2"
|
||||
DEFAULT_MODEL = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/googlenet-12-pimsim-nn.onnx"
|
||||
DEFAULT_COMMON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/common"
|
||||
DEFAULT_COMMON_DIR = REPO_ROOT / "validation/networks/pimcomp_models/googlenet/artifacts/common"
|
||||
SIMULATOR_MANIFEST = REPO_ROOT / "backend-simulators/pim/pim-simulator/Cargo.toml"
|
||||
|
||||
|
||||
@@ -28,6 +27,7 @@ def run_simulator(
|
||||
memory: Path,
|
||||
output: Path,
|
||||
dump: str,
|
||||
input_dir: Path,
|
||||
) -> None:
|
||||
subprocess.run(
|
||||
[
|
||||
@@ -44,6 +44,8 @@ def run_simulator(
|
||||
str(comparison_dir / "pimcomp/exported"),
|
||||
"--memory",
|
||||
str(memory),
|
||||
"--input-dir",
|
||||
str(input_dir),
|
||||
"-o",
|
||||
str(output),
|
||||
"-d",
|
||||
@@ -86,14 +88,18 @@ def main() -> int:
|
||||
model = args.model.resolve()
|
||||
common_dir = args.common_dir.resolve()
|
||||
if args.work_dir is None:
|
||||
work_dir = Path(tempfile.mkdtemp(prefix="pimcomp-prefill."))
|
||||
work_dir = comparison_dir / "correctness/prefill"
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
work_dir = args.work_dir.resolve()
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
input_path = common_dir / "inputs/in0.csv"
|
||||
if not input_path.is_file():
|
||||
input_path = comparison_dir / "inputs/in0.csv"
|
||||
simulator_input_dir = work_dir / "inputs"
|
||||
simulator_input_dir.mkdir(parents=True, exist_ok=True)
|
||||
np.loadtxt(input_path, delimiter=",", dtype=np.float32).tofile(
|
||||
simulator_input_dir / "input_0.bin"
|
||||
)
|
||||
|
||||
prefilled_memory = work_dir / "prefilled_memory.bin"
|
||||
metadata_path = work_dir / "metadata.json"
|
||||
@@ -117,8 +123,15 @@ def main() -> int:
|
||||
comparison_dir / "pimcomp/exported/memory.bin",
|
||||
baseline_output,
|
||||
dump,
|
||||
simulator_input_dir,
|
||||
)
|
||||
run_simulator(
|
||||
comparison_dir,
|
||||
prefilled_memory,
|
||||
prefilled_output,
|
||||
dump,
|
||||
simulator_input_dir,
|
||||
)
|
||||
run_simulator(comparison_dir, prefilled_memory, prefilled_output, dump)
|
||||
compare_outputs(baseline_output, prefilled_output, reference, work_dir)
|
||||
return 0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user