Files
Raptor/validation/raptor_validation/validate_one.py
T
NiccoloN c55d9f3dad add throughput mode to validation scripts
make raptor also emit input sizes
2026-08-11 10:34:50 +02:00

822 lines
34 KiB
Python

import json
import os
import re
import shutil
import subprocess
import sys
import time
import numpy as np
from dataclasses import dataclass, field
from pathlib import Path
from colorama import Style, Fore
from .gen_network_runner import gen_network_runner
from .onnx_utils import (
_ONNX_TO_NP,
gen_random_inputs,
generate_input_batch,
onnx_io,
save_inputs_to_files,
write_input_batch_binaries,
write_input_batch_csv,
write_inputs_to_memory_bin,
)
from .raptor import compile_with_raptor
from .pimsim_nn import export_raptor_pimsim_artifact, parse_pimsim_nn_metrics, read_raptor_instruction_count
from .subprocess_utils import run_command_with_reporter
STAGE_TITLES = (
"Compile ONNX",
"Build Runner",
"Generate Inputs",
"Run Reference",
"Compile PIM",
"Run Functional Simulation",
"Compare Outputs",
"Run Non-functional Simulation",
)
STAGE_COLORS = {
STAGE_TITLES[0]: Fore.BLUE,
STAGE_TITLES[1]: Fore.MAGENTA,
STAGE_TITLES[2]: Fore.YELLOW,
STAGE_TITLES[3]: Fore.GREEN,
STAGE_TITLES[4]: Fore.CYAN,
STAGE_TITLES[5]: Fore.MAGENTA,
STAGE_TITLES[6]: Fore.YELLOW,
STAGE_TITLES[7]: Fore.BLUE,
}
STAGE_COUNT = len(STAGE_TITLES)
GENERATED_DIR_NAMES = (
"inputs", "outputs", "pimcomp", "raptor", "runner", "simulation",
"throughput_validation",
)
MODE_FULL = "full"
MODE_COMPILE_ONLY = "compile_only"
MODE_RUN_ONLY = "run_only"
MODE_STAGE_TITLES = {
MODE_FULL: STAGE_TITLES,
MODE_COMPILE_ONLY: (
"Compile ONNX",
"Build Runner",
"Compile PIM",
),
MODE_RUN_ONLY: (
"Generate Inputs",
"Run Reference",
"Run Functional Simulation",
"Compare Outputs",
"Run Non-functional Simulation",
),
}
PIMSIM_DONE = "DONE"
PIMSIM_FAILED = "ERROR"
PIMSIM_UNSUPPORTED = "UNSUPPORTED"
PIMSIM_SKIPPED = "SKIP"
PIMSIM_NOT_RUN = "-"
PIMSIM_UNSUPPORTED_VSOFTMAX = "pimsim-nn does not support opcode vsoftmax"
class PimSimUnsupportedError(RuntimeError):
pass
def sanitize_output_name(name):
return "".join(ch if ch.isalnum() or ch in "_.-" else "_" for ch in name[:255])
@dataclass
class ValidationResult:
passed: bool
latency_passed: bool | None = None
throughput_passed: bool | None = None
pim_pass_timings: dict[str, float] = field(default_factory=dict)
pimsim_latency_ms: float | None = None
pimsim_throughput_samples_s: float | None = None
pimsim_power_mw: float | None = None
pimsim_energy_pj: float | None = None
pimsim_throughput_average_latency_ms: float | None = None
pimsim_throughput_average_power_mw: float | None = None
pimsim_throughput_average_energy_pj: float | None = None
mode_metrics: dict[str, dict[str, float | int | None]] = field(default_factory=dict)
pimsim_status: str = PIMSIM_SKIPPED
throughput_pimsim_status: str = PIMSIM_SKIPPED
compile_time_s: float | None = None
host_memory_bytes: int | None = None
cores_memory_bytes: int | None = None
used_core_count: int | None = None
used_crossbar_count: int | None = None
_MEMORY_UNITS = {"B": 1, "KB": 1 << 10, "MB": 1 << 20, "GB": 1 << 30}
def collect_pim_resource_metrics(pim_dir):
pim_dir = Path(pim_dir)
report_path = pim_dir.parent / "reports" / "memory_report.txt"
report = report_path.read_text(encoding="utf-8") if report_path.exists() else ""
def memory_bytes(label):
match = re.search(rf"^\s*{re.escape(label)}:\s+([0-9.]+)\s+(B|KB|MB|GB)$", report, re.MULTILINE)
return round(float(match.group(1)) * _MEMORY_UNITS[match.group(2)]) if match else None
with open(pim_dir / "config.json", encoding="utf-8") as f:
config = json.load(f)
used_cores = sum(read_raptor_instruction_count(path) > 0 for path in pim_dir.glob("core_*.pim"))
used_crossbars = sum(sum(groups) for groups in config.get("array_group_map", {}).values())
return {
"host_memory_bytes": memory_bytes("Host memory"),
"cores_memory_bytes": memory_bytes("Local memory after reuse"),
"used_core_count": used_cores,
"used_crossbar_count": used_crossbars,
}
class ProgressReporter:
def __init__(self, total_models, stages_per_model=STAGE_COUNT, enabled=None, verbose=False):
self.total_models = total_models
self.stages_per_model = stages_per_model
self.total_steps = max(1, total_models * stages_per_model)
self.completed_steps = 0
self.passed_models = 0
self.failed_models = 0
self.current_label = ""
self.enabled = (
sys.stdout.isatty() and "CODEX_CI" not in os.environ
if enabled is None else enabled
)
self.verbose = verbose
self.columns = max(1, shutil.get_terminal_size((100, 20)).columns)
self.suspended = False
self.rendered_width = 0
self.rendered_rows = 0
def _clear(self):
if self.enabled and self.rendered_rows:
columns = max(1, shutil.get_terminal_size((100, 20)).columns)
rows = max(self.rendered_rows, (self.rendered_width + columns - 1) // columns)
sys.stdout.write("\r\033[2K")
for _ in range(rows - 1):
sys.stdout.write("\033[1A\r\033[2K")
sys.stdout.flush()
self.rendered_width = 0
self.rendered_rows = 0
def _render(self):
if not self.enabled or self.suspended:
return
self.columns = max(1, shutil.get_terminal_size((100, 20)).columns)
bar_width = min(24, max(4, self.columns - 24))
filled = int(bar_width * self.completed_steps / self.total_steps)
counts_text = f"P:{self.passed_models} F:{self.failed_models}"
prefix_text = f"[{'#' * filled}{'-' * (bar_width - filled)}] {self.completed_steps}/{self.total_steps}"
bar = Fore.GREEN + ("#" * filled) + Fore.CYAN + ("-" * (bar_width - filled))
prefix = Fore.CYAN + f"[{bar}{Fore.CYAN}] {self.completed_steps}/{self.total_steps}" + Style.RESET_ALL
counts = (
" "
+ Style.BRIGHT
+ Fore.GREEN
+ f"P:{self.passed_models}"
+ Style.RESET_ALL
+ " "
+ Style.BRIGHT
+ Fore.RED
+ f"F:{self.failed_models}"
+ Style.RESET_ALL
)
model_counter = ""
label = ""
if self.current_label.startswith("[") and "] " in self.current_label:
model_counter, label = self.current_label.split("] ", 1)
model_counter = f" {model_counter}]"
label = f" {label}"
elif self.current_label:
label = f" {self.current_label}"
fixed_width = len(prefix_text) + len(model_counter) + len(counts_text) + 2
if fixed_width > self.columns:
model_counter = ""
fixed_width = len(prefix_text) + len(counts_text) + 2
if fixed_width > self.columns:
prefix_text = f"{self.completed_steps}/{self.total_steps}"
prefix = Fore.CYAN + prefix_text + Style.RESET_ALL
fixed_width = len(prefix_text) + len(counts_text) + 2
if fixed_width > self.columns:
counts = ""
counts_text = ""
fixed_width = len(prefix_text) + 1
available_label_width = max(0, self.columns - fixed_width)
label = label[:available_label_width]
plain_counts = f" {counts_text}" if counts_text else ""
plain_line = prefix_text + model_counter + plain_counts + label
rendered_line = prefix + model_counter + counts + label + Style.RESET_ALL
self._clear()
sys.stdout.write(rendered_line)
sys.stdout.flush()
self.rendered_width = len(plain_line)
self.rendered_rows = max(1, (self.rendered_width + self.columns - 1) // self.columns)
def log(self, message="", color=None):
if not self.verbose:
self._render()
return
if self.enabled:
self._clear()
if color:
print(color + message + Style.RESET_ALL, flush=True)
else:
print(message, flush=True)
self._render()
def set_stage(self, model_index, model_total, model_name, stage_name):
self.current_label = f"[{model_index}/{model_total}] {model_name} · {stage_name}"
self._render()
def advance(self):
self.completed_steps = min(self.total_steps, self.completed_steps + 1)
self._render()
def record_result(self, passed):
if passed:
self.passed_models += 1
else:
self.failed_models += 1
self._render()
def suspend(self):
if self.enabled:
self._clear()
self.suspended = True
def resume(self):
self.suspended = False
self._render()
def finish(self):
if self.enabled:
self.suspended = True
self._clear()
def run_command(cmd, cwd=None, reporter=None, timeout_sec=None, capture_output=False):
return run_command_with_reporter(
cmd,
cwd=cwd,
reporter=reporter,
timeout_sec=timeout_sec,
capture_output=capture_output,
)
def load_pimcomp_hardware(config_path):
with open(config_path, encoding="utf-8") as f:
config = json.load(f)
matrix = config["chip_config"]["core_config"]["matrix_config"]
rows, cols = config["chip_config"]["network_config"]["layout"]
xbar_rows, xbar_cols = matrix["xbar_size"]
return {
"core_count": config["chip_config"]["core_cnt"],
"crossbar_count": matrix["xbar_array_count"],
"crossbar_rows": xbar_rows,
"crossbar_cols": xbar_cols,
"mesh_rows": rows,
"mesh_cols": cols,
}
def pimcomp_compatibility_errors(config_path, *, core_count, crossbar_count, crossbar_size):
hardware = load_pimcomp_hardware(config_path)
errors = []
if hardware["mesh_rows"] * hardware["mesh_cols"] != hardware["core_count"]:
errors.append(
f"config layout {hardware['mesh_rows']}x{hardware['mesh_cols']} does not match "
f"{hardware['core_count']} cores"
)
if core_count != hardware["core_count"]:
errors.append(f"--core-count={core_count}, config requires {hardware['core_count']}")
if crossbar_count != hardware["crossbar_count"]:
errors.append(
f"--crossbar-count={crossbar_count}, config requires {hardware['crossbar_count']}"
)
if (
hardware["crossbar_rows"] != hardware["crossbar_cols"]
or crossbar_size != hardware["crossbar_rows"]
):
errors.append(
f"--crossbar-size={crossbar_size}, config requires "
f"{hardware['crossbar_rows']}x{hardware['crossbar_cols']}"
)
return errors
def run_pimsim_nn(
pimsim_nn_build_dir, pim_dir, config_path, execution_mode,
reporter=None, timeout_sec=None, fast=True):
pimsim_artifact = export_raptor_pimsim_artifact(pim_dir, Path(pim_dir).parent / "pimsim_nn")
command = [pimsim_nn_build_dir / "ChipTest", pimsim_artifact, config_path, "--gui=false"]
if fast:
command.append("--fast")
try:
output = run_command(
command,
cwd=pimsim_nn_build_dir,
reporter=reporter,
timeout_sec=timeout_sec,
capture_output=True,
)
except subprocess.CalledProcessError as exc:
error_output = exc.output.decode("utf-8", errors="replace") if isinstance(exc.output, bytes) else str(exc.output)
if PIMSIM_UNSUPPORTED_VSOFTMAX in error_output:
raise PimSimUnsupportedError(PIMSIM_UNSUPPORTED_VSOFTMAX) from exc
raise
metrics = parse_pimsim_nn_metrics(output)
required = (
("latency_ms", "average_power_mw", "average_energy_pj")
if execution_mode == "latency"
else ("throughput", "average_latency_ms", "average_power_mw", "average_energy_pj")
)
if any(name not in metrics for name in required):
raise RuntimeError(f"pimsim-nn output did not contain required {execution_mode} metrics")
return metrics
def clean_workspace_artifacts(workspace_dir, model_stem):
workspace_dir = Path(workspace_dir)
removed_paths = []
def remove_path(path):
if path.is_symlink() or path.is_file():
path.unlink(missing_ok=True)
removed_paths.append(path)
elif path.is_dir():
shutil.rmtree(path)
removed_paths.append(path)
for name in GENERATED_DIR_NAMES:
remove_path(workspace_dir / name)
remove_path(workspace_dir / "inputs.csv")
for suffix in (".onnx.mlir", ".so", ".tmp"):
remove_path(workspace_dir / f"{model_stem}{suffix}")
return removed_paths
def print_stage(reporter, model_index, model_total, model_name, title, mode=None):
if mode is not None:
title = f"{title} ({mode.capitalize()})"
color = STAGE_COLORS.get(title, STAGE_COLORS.get(title.split(" (", 1)[0], Fore.WHITE))
reporter.log(Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL)
reporter.set_stage(model_index, model_total, model_name, title)
def print_info(reporter, message):
reporter.log(f" {message}")
def compile_onnx_network(network_onnx_path, raptor_path, raptor_dir, runner_dir, reporter=None, timeout_sec=None):
stem = network_onnx_path.stem
onnx_ir_base = raptor_dir / stem
runner_base = runner_dir / stem
run_command([raptor_path, network_onnx_path, "-o", onnx_ir_base, "--EmitONNXIR",
"--mlir-elide-elementsattrs-if-larger=16"],
reporter=reporter, timeout_sec=timeout_sec)
run_command([raptor_path, network_onnx_path, "-o", runner_base], reporter=reporter, timeout_sec=timeout_sec)
network_so_path = runner_base.with_suffix(".so")
network_mlir_path = onnx_ir_base.with_suffix(".onnx.mlir")
onnx_ir_base.with_suffix(".tmp").unlink(missing_ok=True)
return network_so_path, network_mlir_path
def build_onnx_runner(source_dir, build_dir, reporter=None, timeout_sec=None):
run_command(["cmake", source_dir], cwd=build_dir, reporter=reporter, timeout_sec=timeout_sec)
run_command(["cmake", "--build", ".", "-j"], cwd=build_dir, reporter=reporter, timeout_sec=timeout_sec)
return build_dir / "runner"
def build_dump_ranges(config_path, outputs_descriptor):
with open(config_path) as f:
output_addresses = json.load(f)["outputs_addresses"]
ranges = []
for addr, (_, _, dtype_code, shape) in zip(output_addresses, outputs_descriptor):
byte_size = int(np.prod(shape)) * np.dtype(_ONNX_TO_NP[dtype_code]).itemsize
ranges.append(f"{addr},{byte_size}")
return ",".join(ranges)
def build_pim_simulator_command(
pim_dir, output_bin_path, dump_ranges, input_paths, mode="latency",
batch_output_dir=None):
if mode not in ("latency", "throughput"):
raise ValueError(f"unknown simulator mode: {mode}")
if not input_paths:
raise ValueError("simulator requires at least one input")
command = [
"cargo", "run", "--no-default-features", "--release", "--package", "pim-simulator", "--bin", "pim-simulator",
"--", "-f", str(pim_dir), "-o", str(output_bin_path), "-d", dump_ranges,
"--mode", mode, "--batch-size", str(len(input_paths)),
]
if batch_output_dir is not None:
command += ["--batch-output-dir", str(batch_output_dir)]
for path in input_paths:
command += ["--input", str(path)]
return command
def run_pim_simulator(
simulator_dir, pim_dir, output_bin_path, dump_ranges, reporter=None,
timeout_sec=None, input_paths=(), mode="latency", batch_output_dir=None):
command = build_pim_simulator_command(
pim_dir,
output_bin_path,
dump_ranges,
input_paths,
mode=mode,
batch_output_dir=batch_output_dir,
)
run_command(
command,
cwd=simulator_dir,
reporter=reporter,
timeout_sec=timeout_sec,
)
def parse_pim_simulator_outputs(output_bin_path, outputs_descriptor):
raw = output_bin_path.read_bytes()
arrays = []
offset = 0
for _, _, dtype_code, shape in outputs_descriptor:
dtype = np.dtype(_ONNX_TO_NP[dtype_code])
count = int(np.prod(shape))
array = np.frombuffer(raw, dtype=dtype, count=count, offset=offset).reshape(shape)
offset += count * dtype.itemsize
arrays.append(array)
return arrays
def validate_outputs(sim_arrays, runner_out_dir, outputs_descriptor, threshold, rtol, verbose):
all_passed = True
rows = []
for sim_array, (oi, name, _, shape) in zip(sim_arrays, outputs_descriptor):
csv_name = f"output{oi}_{sanitize_output_name(name)}.csv"
runner_array = np.loadtxt(runner_out_dir / csv_name, delimiter=',', dtype=np.float32).reshape(shape)
sim_array64 = sim_array.astype(np.float64)
runner_array64 = runner_array.astype(np.float64)
abs_diff = np.abs(sim_array64 - runner_array64)
allowed_diff = threshold + rtol * np.abs(runner_array64)
max_diff = float(np.max(abs_diff))
passed = bool(np.all(abs_diff <= allowed_diff))
rows.append((name, f"{max_diff:.6e}", passed))
if not passed:
all_passed = False
name_width = max(len("Output"), *(len(name) for name, _, _ in rows))
diff_width = max(len("Max diff"), *(len(diff) for _, diff, _ in rows))
result_width = len("Result")
separator = f" +-{'-' * name_width}-+-{'-' * diff_width}-+-{'-' * result_width}-+"
if verbose or not all_passed:
print(separator)
print(f" | {'Output'.ljust(name_width)} | {'Max diff'.ljust(diff_width)} | {'Result'} |")
print(separator)
for name, diff_text, passed in rows:
status_text = ("PASS" if passed else "FAIL").ljust(result_width)
status = Fore.GREEN + status_text + Style.RESET_ALL if passed else Fore.RED + status_text + Style.RESET_ALL
print(f" | {name.ljust(name_width)} | {diff_text.ljust(diff_width)} | {status} |")
print(separator)
return all_passed
def report_validation_failure(reporter, execution_name, stage, exc):
reporter.suspend()
print(
Fore.RED + f"{execution_name.capitalize()} {stage} failed: "
f"{type(exc).__name__}: {exc}" + Style.RESET_ALL,
file=sys.stderr,
flush=True,
)
reporter.resume()
def validate_execution(
execution, state, functional_data, workspace_dir, simulator_dir,
pimsim_nn_build_dir, threshold, rtol, verbose, command_timeout_seconds,
stage_context, pimsim_fast):
reporter, model_index, model_total, model_name = stage_context
name = execution["name"]
pim_dir = execution["root"] / "pim"
batch_size = execution["batch_size"]
if state["compiled"] and functional_data is not None:
input_batch, input_paths, reference_dirs, outputs_descriptor = functional_data
simulation_dir = workspace_dir / "simulation" / name
try:
print_stage(
reporter, model_index, model_total, model_name,
"Run Functional Simulation", name,
)
write_inputs_to_memory_bin(
pim_dir / "memory.bin", pim_dir / "config.json", input_batch[0])
simulation_dir.mkdir(parents=True, exist_ok=True)
dump_ranges = build_dump_ranges(pim_dir / "config.json", outputs_descriptor)
output_dir = simulation_dir / "outputs"
run_pim_simulator(
simulator_dir, pim_dir, simulation_dir / "out.bin", dump_ranges,
reporter=reporter, timeout_sec=command_timeout_seconds,
input_paths=input_paths[:batch_size], mode=name,
batch_output_dir=output_dir)
reporter.advance()
print_stage(
reporter, model_index, model_total, model_name,
"Compare Outputs", name,
)
reporter.suspend()
try:
iteration_results = [
validate_outputs(
parse_pim_simulator_outputs(
output_dir / f"output_{index:06d}.bin", outputs_descriptor),
reference_dirs[index], outputs_descriptor,
threshold, rtol=rtol, verbose=verbose)
for index in range(batch_size)
]
finally:
reporter.resume()
state["passed"] = all(iteration_results)
reporter.advance()
except Exception as exc:
report_validation_failure(reporter, name, "functional validation", exc)
print_stage(
reporter, model_index, model_total, model_name,
"Run Non-functional Simulation", name,
)
config_path = execution["pimsim_config"]
if state["compiled"] and pimsim_nn_build_dir is not None and config_path is not None:
try:
state["metrics"] = run_pimsim_nn(
pimsim_nn_build_dir, pim_dir, config_path, name,
reporter=reporter, timeout_sec=command_timeout_seconds,
fast=pimsim_fast)
state["pimsim_status"] = PIMSIM_DONE
metric = (
f"Latency: {state['metrics']['latency_ms']:.2f} ms"
if name == "latency" else
f"Throughput: {state['metrics']['throughput']:.2f} samples/s")
energy_unit = "pJ" if name == "latency" else "pJ/it"
print_info(
reporter, f"{metric}, Power: {state['metrics']['average_power_mw']:.2f} mW, "
f"Energy: {state['metrics']['average_energy_pj']:.2f} {energy_unit}")
except PimSimUnsupportedError as exc:
state["pimsim_status"] = PIMSIM_UNSUPPORTED
print_info(reporter, str(exc))
except Exception as exc:
state["pimsim_status"] = PIMSIM_FAILED
report_validation_failure(reporter, name, "non-functional validation", exc)
elif not state["compiled"]:
state["pimsim_status"] = PIMSIM_NOT_RUN
else:
print_info(reporter, "pimsim-nn non-functional simulation skipped")
reporter.advance()
def validate_network(network_onnx_path, raptor_path, onnx_include_dir,
simulator_dir, crossbar_size, crossbar_count, core_count,
raptor_extra_args,
pimsim_nn_build_dir, pimsim_config_path,
threshold, rtol,
seed, reporter, model_index, model_total, verbose,
command_timeout_seconds, mode, throughput_pipeline=None,
throughput_batch_size=4, throughput_pimsim_config_path=None,
pimsim_fast=True):
if throughput_pipeline is not None and throughput_batch_size < 2:
raise ValueError("throughput validation requires batch size greater than 1")
network_onnx_path = Path(network_onnx_path).resolve()
raptor_path = Path(raptor_path).resolve()
onnx_include_dir = Path(onnx_include_dir).resolve()
simulator_dir = Path(simulator_dir).resolve()
if pimsim_nn_build_dir is not None:
pimsim_nn_build_dir = Path(pimsim_nn_build_dir).resolve()
if pimsim_config_path is not None:
pimsim_config_path = Path(pimsim_config_path).resolve()
if throughput_pimsim_config_path is not None:
throughput_pimsim_config_path = Path(throughput_pimsim_config_path).resolve()
compile_extra_args = list(raptor_extra_args or [])
owns_reporter = reporter is None
reporter = reporter or ProgressReporter(model_total, stages_per_model=len(MODE_STAGE_TITLES[mode]), verbose=verbose)
workspace_dir = network_onnx_path.parent
raptor_dir = workspace_dir / "raptor"
runner_dir = workspace_dir / "runner"
runner_build_dir = runner_dir / "build"
if mode != MODE_RUN_ONLY:
clean_workspace_artifacts(workspace_dir, network_onnx_path.stem)
Path.mkdir(raptor_dir, parents=True, exist_ok=True)
Path.mkdir(runner_build_dir, parents=True, exist_ok=True)
reporter.log(Fore.CYAN + f"[{model_index}/{model_total}]" + Style.RESET_ALL +
f" {Style.BRIGHT}Validating {network_onnx_path.name}{Style.RESET_ALL}")
stem = network_onnx_path.stem
network_so_path = runner_dir / f"{stem}.so"
network_mlir_path = raptor_dir / f"{stem}.onnx.mlir"
runner_path = runner_build_dir / "runner"
executions = [{
"name": "latency",
"root": raptor_dir,
"batch_size": 1,
"compile_args": compile_extra_args,
"pimsim_config": pimsim_config_path,
}]
if throughput_pipeline is not None:
throughput_args = [
arg for arg in compile_extra_args if not str(arg).startswith("--pipeline=")
] + [f"--pipeline={throughput_pipeline}"]
executions.append({
"name": "throughput",
"root": raptor_dir / "throughput",
"batch_size": throughput_batch_size,
"compile_args": throughput_args,
"pimsim_config": throughput_pimsim_config_path,
})
states = {
execution["name"]: {
"compiled": False,
"passed": False,
"metrics": {},
"pimsim_status": PIMSIM_SKIPPED,
"compile_time_s": 0.0,
"resource_metrics": {},
}
for execution in executions
}
pim_pass_timings = {}
compile_time_s = 0.0
resource_metrics = {}
try:
reference_ready = False
if mode != MODE_RUN_ONLY:
try:
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Compile ONNX")
network_so_path, network_mlir_path = compile_onnx_network(
network_onnx_path, raptor_path, raptor_dir, runner_dir,
reporter=reporter, timeout_sec=command_timeout_seconds)
print_info(reporter, f"MLIR saved to {network_mlir_path}")
print_info(reporter, f"Shared library saved to {network_so_path}")
reporter.advance()
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Build Runner")
gen_network_runner(
network_onnx_path, network_so_path, onnx_include_dir,
entry="run_main_graph", out=runner_dir / "runner.c", verbose=False)
runner_path = build_onnx_runner(
runner_dir, runner_build_dir, reporter=reporter,
timeout_sec=command_timeout_seconds)
print_info(reporter, f"Runner built at {runner_path}")
reporter.advance()
reference_ready = True
except Exception as exc:
report_validation_failure(reporter, "reference", "compilation", exc)
else:
required_paths = (network_so_path, network_mlir_path, runner_path)
reference_ready = all(path.exists() for path in required_paths)
if not reference_ready:
report_validation_failure(reporter, "reference", "artifact lookup", FileNotFoundError(
"run-only mode requires the compiled shared library, ONNX MLIR, and runner"))
for execution in executions:
name = execution["name"]
root = execution["root"]
pim_dir = root / "pim"
if mode == MODE_RUN_ONLY:
states[name]["compiled"] = (pim_dir / "config.json").exists()
if not states[name]["compiled"]:
report_validation_failure(reporter, name, "artifact lookup", FileNotFoundError(
f"run-only mode requires compiled PIM artifacts at {pim_dir}"))
else:
states[name]["resource_metrics"] = collect_pim_resource_metrics(pim_dir)
if name == "latency":
resource_metrics = states[name]["resource_metrics"]
continue
try:
print_stage(
reporter, model_index, model_total, network_onnx_path.name,
"Compile PIM", name,
)
root.mkdir(parents=True, exist_ok=True)
started = time.perf_counter()
timings = compile_with_raptor(
network_onnx_path, raptor_path, root / stem, crossbar_size,
crossbar_count, core_count=core_count,
raptor_extra_args=execution["compile_args"], cwd=root,
verbose=verbose, reporter=reporter,
timeout_sec=command_timeout_seconds)
elapsed = time.perf_counter() - started
compile_time_s += elapsed
states[name]["compile_time_s"] = elapsed
for label, duration in timings.items():
pim_pass_timings[label] = pim_pass_timings.get(label, 0) + duration
states[name]["compiled"] = True
states[name]["resource_metrics"] = collect_pim_resource_metrics(pim_dir)
if name == "latency":
resource_metrics = states[name]["resource_metrics"]
print_info(reporter, f"PIM artifacts saved to {pim_dir}")
except Exception as exc:
report_validation_failure(reporter, name, "compilation", exc)
reporter.advance()
if mode == MODE_COMPILE_ONLY:
for state in states.values():
state["passed"] = reference_ready and state["compiled"]
else:
input_batch = input_paths = reference_dirs = outputs_descriptor = None
try:
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Generate Inputs")
inputs_descriptor, outputs_descriptor = onnx_io(network_onnx_path)
first_inputs, _ = gen_random_inputs(inputs_descriptor, seed=seed)
input_batch = generate_input_batch(
inputs_descriptor, first_inputs,
max(execution["batch_size"] for execution in executions), seed)
write_input_batch_csv(workspace_dir / "inputs.csv", input_batch)
input_paths = write_input_batch_binaries(input_batch, workspace_dir / "simulation" / "inputs")
input_flags = [
save_inputs_to_files(
network_onnx_path, inputs,
out_dir=workspace_dir / "inputs" / f"{index:06d}")[0]
for index, inputs in enumerate(input_batch)
]
print_info(reporter, f"Saved {len(input_batch)} input sample(s) to {workspace_dir / 'inputs.csv'}")
reporter.advance()
if not reference_ready:
raise FileNotFoundError("reference runner is unavailable")
print_stage(reporter, model_index, model_total, network_onnx_path.name, "Run Reference")
reference_dirs = []
for index, flags in enumerate(input_flags):
reference_dir = workspace_dir / "outputs" / f"{index:06d}"
reference_dir.mkdir(parents=True, exist_ok=True)
run_command(
[runner_path, *flags, "--save-csv-dir", str(reference_dir)],
cwd=runner_build_dir, reporter=reporter,
timeout_sec=command_timeout_seconds)
reference_dirs.append(reference_dir)
print_info(reporter, f"Reference outputs saved for {len(reference_dirs)} sample(s)")
reporter.advance()
except Exception as exc:
report_validation_failure(reporter, "reference", "execution", exc)
functional_data = None
if all(value is not None for value in (
input_batch, input_paths, reference_dirs, outputs_descriptor)):
functional_data = input_batch, input_paths, reference_dirs, outputs_descriptor
stage_context = reporter, model_index, model_total, network_onnx_path.name
for execution in executions:
validate_execution(
execution, states[execution["name"]], functional_data,
workspace_dir, simulator_dir, pimsim_nn_build_dir,
threshold, rtol, verbose, command_timeout_seconds,
stage_context, pimsim_fast)
latency = states["latency"]
throughput = states.get("throughput")
passed = all(state["passed"] for state in states.values())
latency_metrics = latency["metrics"]
throughput_metrics = throughput["metrics"] if throughput else {}
reporter.record_result(passed)
status = Fore.GREEN + "PASS" + Style.RESET_ALL if passed else Fore.RED + "FAIL" + Style.RESET_ALL
reporter.log(Style.BRIGHT + f"Result: {status}" + Style.RESET_ALL)
mode_metrics = {
name: {
"compile_time_s": state["compile_time_s"] or None,
**state["resource_metrics"],
}
for name, state in states.items()
}
return ValidationResult(
passed=passed,
latency_passed=latency["passed"],
throughput_passed=throughput["passed"] if throughput else None,
pim_pass_timings=pim_pass_timings,
pimsim_latency_ms=latency_metrics.get("latency_ms"),
pimsim_throughput_samples_s=throughput_metrics.get("throughput"),
pimsim_power_mw=latency_metrics.get("average_power_mw"),
pimsim_energy_pj=latency_metrics.get("average_energy_pj"),
pimsim_throughput_average_latency_ms=throughput_metrics.get("average_latency_ms"),
pimsim_throughput_average_power_mw=throughput_metrics.get("average_power_mw"),
pimsim_throughput_average_energy_pj=throughput_metrics.get("average_energy_pj"),
mode_metrics=mode_metrics,
pimsim_status=latency["pimsim_status"],
throughput_pimsim_status=(
throughput["pimsim_status"] if throughput else PIMSIM_SKIPPED),
compile_time_s=compile_time_s or None,
**resource_metrics,
)
finally:
reporter.log("=" * 72)
if owns_reporter:
reporter.finish()