This commit is contained in:
@@ -145,8 +145,13 @@ def run_logged(
|
|||||||
timeout_sec: float,
|
timeout_sec: float,
|
||||||
steps: list[StepRecord],
|
steps: list[StepRecord],
|
||||||
stage: str | None = None,
|
stage: str | None = None,
|
||||||
|
print_header: bool = True,
|
||||||
) -> str:
|
) -> str:
|
||||||
print_step(name, cmd, cwd, stage)
|
if print_header:
|
||||||
|
print_step(name, cmd, cwd, stage)
|
||||||
|
else:
|
||||||
|
print(f" cwd: {cwd or REPO}")
|
||||||
|
print(f" $ {shell_join(cmd)}")
|
||||||
start = time.perf_counter()
|
start = time.perf_counter()
|
||||||
command = shell_join(cmd)
|
command = shell_join(cmd)
|
||||||
try:
|
try:
|
||||||
@@ -446,7 +451,7 @@ def compile_raptor_target(
|
|||||||
"--pim-emit-json",
|
"--pim-emit-json",
|
||||||
*args.raptor_extra_arg,
|
*args.raptor_extra_arg,
|
||||||
]
|
]
|
||||||
print_step("Compile Raptor PIM", cmd, REPO, "Compile PIM")
|
print_step("Compile Raptor", cmd, REPO, "Compile PIM")
|
||||||
start = time.perf_counter()
|
start = time.perf_counter()
|
||||||
command = shell_join(cmd)
|
command = shell_join(cmd)
|
||||||
raptor_extra_args = [
|
raptor_extra_args = [
|
||||||
@@ -566,6 +571,7 @@ def compile_pimcomp(
|
|||||||
(pimcomp_output_dir / name).unlink(missing_ok=True)
|
(pimcomp_output_dir / name).unlink(missing_ok=True)
|
||||||
model_name = args.pimcomp_model_name or f"compare_{model_path.stem}"
|
model_name = args.pimcomp_model_name or f"compare_{model_path.stem}"
|
||||||
frontend_json = frontend_json_dir / f"{model_name}.json"
|
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.
|
# The original PIMCOMP frontend rewrites its input ONNX while loading it.
|
||||||
# Isolate that mutation without changing the model sent through PIMCOMP.
|
# Isolate that mutation without changing the model sent through PIMCOMP.
|
||||||
with TemporaryDirectory(prefix="pimcomp-model-") as temp_dir:
|
with TemporaryDirectory(prefix="pimcomp-model-") as temp_dir:
|
||||||
@@ -586,6 +592,7 @@ def compile_pimcomp(
|
|||||||
timeout_sec=args.timeout_seconds,
|
timeout_sec=args.timeout_seconds,
|
||||||
steps=steps,
|
steps=steps,
|
||||||
stage="Compile PIM",
|
stage="Compile PIM",
|
||||||
|
print_header=False,
|
||||||
)
|
)
|
||||||
backend_cmd = [
|
backend_cmd = [
|
||||||
str(args.pimcomp_dir / "build" / "PIMCOMP-NN"),
|
str(args.pimcomp_dir / "build" / "PIMCOMP-NN"),
|
||||||
@@ -603,6 +610,7 @@ def compile_pimcomp(
|
|||||||
timeout_sec=args.timeout_seconds,
|
timeout_sec=args.timeout_seconds,
|
||||||
steps=steps,
|
steps=steps,
|
||||||
stage="Compile PIM",
|
stage="Compile PIM",
|
||||||
|
print_header=False,
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(frontend_json_dir.parent, ignore_errors=True)
|
shutil.rmtree(frontend_json_dir.parent, ignore_errors=True)
|
||||||
@@ -1014,6 +1022,28 @@ def optional_path(path: Path | None) -> str | None:
|
|||||||
return str(path) if path is not None else None
|
return str(path) if path is not None else None
|
||||||
|
|
||||||
|
|
||||||
|
def path_from_report(report: dict[str, Any], name: str) -> Path | None:
|
||||||
|
value = report.get("paths", {}).get(name)
|
||||||
|
return Path(value) if value else None
|
||||||
|
|
||||||
|
|
||||||
|
def restore_side_records(
|
||||||
|
report: dict[str, Any],
|
||||||
|
side: str,
|
||||||
|
failures: list[dict[str, str]],
|
||||||
|
steps: list[StepRecord],
|
||||||
|
) -> None:
|
||||||
|
marker = side.upper()
|
||||||
|
failures.extend(
|
||||||
|
failure for failure in report.get("failures", [])
|
||||||
|
if marker in failure.get("stage", "").upper()
|
||||||
|
)
|
||||||
|
steps.extend(
|
||||||
|
StepRecord(**step) for step in report.get("steps", [])
|
||||||
|
if marker in step.get("name", "").upper()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def default_common_dir(out_dir: Path) -> Path:
|
def default_common_dir(out_dir: Path) -> Path:
|
||||||
if out_dir.name == "latency":
|
if out_dir.name == "latency":
|
||||||
return out_dir.parents[1] / "common"
|
return out_dir.parents[1] / "common"
|
||||||
@@ -1280,6 +1310,11 @@ def main():
|
|||||||
type=Path,
|
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.",
|
||||||
|
)
|
||||||
parser.add_argument("--skip-pimsim-nn", action="store_true")
|
parser.add_argument("--skip-pimsim-nn", action="store_true")
|
||||||
parser.add_argument("--verbose-raptor-compile", action="store_true")
|
parser.add_argument("--verbose-raptor-compile", action="store_true")
|
||||||
parser.add_argument("--raptor-extra-arg", action="append", default=[])
|
parser.add_argument("--raptor-extra-arg", action="append", default=[])
|
||||||
@@ -1289,6 +1324,8 @@ def main():
|
|||||||
help="Return a non-zero status if a stage or semantic validation fails.",
|
help="Return a non-zero status if a stage or semantic validation fails.",
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
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:
|
if args.pimsim_time_ms <= 0:
|
||||||
parser.error("--pimsim-time-ms must be positive")
|
parser.error("--pimsim-time-ms must be positive")
|
||||||
if args.timeout_seconds < 0:
|
if args.timeout_seconds < 0:
|
||||||
@@ -1340,6 +1377,7 @@ def main():
|
|||||||
verification_info: Path | None = None
|
verification_info: Path | None = None
|
||||||
simulation_info: Path | None = None
|
simulation_info: Path | None = None
|
||||||
pimcomp_export_dir: Path | None = None
|
pimcomp_export_dir: Path | None = None
|
||||||
|
pimcomp_pimsim_dir: Path | None = None
|
||||||
pimcomp_model_path: Path | None = None
|
pimcomp_model_path: Path | None = None
|
||||||
pimsim_config: Path | None = None
|
pimsim_config: Path | None = None
|
||||||
reuse_raptor = args.reuse_raptor_report is not None
|
reuse_raptor = args.reuse_raptor_report is not None
|
||||||
@@ -1360,11 +1398,14 @@ def main():
|
|||||||
inputs_desc, outputs_desc, arrays_in_order = model_io
|
inputs_desc, outputs_desc, arrays_in_order = model_io
|
||||||
runtime_inputs = arrays_in_order
|
runtime_inputs = arrays_in_order
|
||||||
|
|
||||||
reuse_pimcomp = args.reuse_pimcomp_dir is not None
|
reused_pimcomp_report_path = args.reuse_pimcomp_report
|
||||||
if reuse_pimcomp:
|
if args.reuse_pimcomp_dir is not None:
|
||||||
reused_pimcomp_report_path = args.reuse_pimcomp_dir.resolve().parent / "comparison_report.json"
|
reused_pimcomp_report_path = args.reuse_pimcomp_dir.resolve().parent / "comparison_report.json"
|
||||||
|
reuse_pimcomp = reused_pimcomp_report_path is not None
|
||||||
|
if reuse_pimcomp:
|
||||||
|
reused_pimcomp_report_path = reused_pimcomp_report_path.resolve()
|
||||||
if not reused_pimcomp_report_path.exists():
|
if not reused_pimcomp_report_path.exists():
|
||||||
raise ValueError(f"Missing PIMCOMP report beside {args.reuse_pimcomp_dir}")
|
raise ValueError(f"Missing PIMCOMP report: {reused_pimcomp_report_path}")
|
||||||
with open(reused_pimcomp_report_path, "r", encoding="utf-8") as f:
|
with open(reused_pimcomp_report_path, "r", encoding="utf-8") as f:
|
||||||
reused_pimcomp = json.load(f)
|
reused_pimcomp = json.load(f)
|
||||||
if reused_pimcomp.get("pimcomp_model_source") != "original_onnx":
|
if reused_pimcomp.get("pimcomp_model_source") != "original_onnx":
|
||||||
@@ -1372,9 +1413,13 @@ def main():
|
|||||||
pimcomp_validation = CompareResult(**reused_pimcomp["pimcomp_validation"])
|
pimcomp_validation = CompareResult(**reused_pimcomp["pimcomp_validation"])
|
||||||
pimcomp_perf = reused_pimcomp["pimcomp_performance"]
|
pimcomp_perf = reused_pimcomp["pimcomp_performance"]
|
||||||
pimcomp_instr = reused_pimcomp["pimcomp_instruction_summary"]
|
pimcomp_instr = reused_pimcomp["pimcomp_instruction_summary"]
|
||||||
reused_config = reused_pimcomp.get("paths", {}).get("pimsim_config")
|
simulation_info = path_from_report(reused_pimcomp, "pimcomp_simulation_info")
|
||||||
if reused_config:
|
pimcomp_export_dir = path_from_report(reused_pimcomp, "pimcomp_exported_pim")
|
||||||
pimsim_config = Path(reused_config)
|
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)
|
||||||
|
|
||||||
if reuse_raptor and model_io is not None:
|
if reuse_raptor and model_io is not None:
|
||||||
reuse_report_path = args.reuse_raptor_report.resolve()
|
reuse_report_path = args.reuse_raptor_report.resolve()
|
||||||
@@ -1384,8 +1429,9 @@ def main():
|
|||||||
if reused_hardware != hardware:
|
if reused_hardware != hardware:
|
||||||
raise ValueError(f"Reused Raptor hardware differs: {reused_hardware} != {hardware}")
|
raise ValueError(f"Reused Raptor hardware differs: {reused_hardware} != {hardware}")
|
||||||
reference_dir = common_dir / "outputs"
|
reference_dir = common_dir / "outputs"
|
||||||
reused_raptor_pim = reused["paths"].get("raptor_pim")
|
runner_path = path_from_report(reused, "reference_runner")
|
||||||
raptor_pim_dir = Path(reused_raptor_pim) if reused_raptor_pim else None
|
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(
|
arrays_in_order = load_saved_inputs(
|
||||||
inputs_desc,
|
inputs_desc,
|
||||||
common_dir / "inputs",
|
common_dir / "inputs",
|
||||||
@@ -1395,6 +1441,7 @@ def main():
|
|||||||
raptor_perf = reused["raptor_performance"]
|
raptor_perf = reused["raptor_performance"]
|
||||||
raptor_instr = reused["raptor_instruction_summary"]
|
raptor_instr = reused["raptor_instruction_summary"]
|
||||||
raptor_pass_timings = reused["raptor_pass_timings"]
|
raptor_pass_timings = reused["raptor_pass_timings"]
|
||||||
|
restore_side_records(reused, "raptor", failures, steps)
|
||||||
print_step("Reuse Raptor")
|
print_step("Reuse Raptor")
|
||||||
print(f" Report: {reuse_report_path}")
|
print(f" Report: {reuse_report_path}")
|
||||||
|
|
||||||
@@ -1528,7 +1575,7 @@ def main():
|
|||||||
simulation_info = out_dir / "pimcomp/output/SimulationInfo.gz"
|
simulation_info = out_dir / "pimcomp/output/SimulationInfo.gz"
|
||||||
print_step("Reuse PIMCOMP")
|
print_step("Reuse PIMCOMP")
|
||||||
print(f" Directory: {reused_pimcomp_dir}")
|
print(f" Directory: {reused_pimcomp_dir}")
|
||||||
else:
|
elif not reuse_pimcomp:
|
||||||
compiled_pimcomp = try_stage(
|
compiled_pimcomp = try_stage(
|
||||||
failures,
|
failures,
|
||||||
"Compile PIMCOMP",
|
"Compile PIMCOMP",
|
||||||
@@ -1614,11 +1661,13 @@ def main():
|
|||||||
if args.skip_pimsim_nn:
|
if args.skip_pimsim_nn:
|
||||||
if not reuse_raptor:
|
if not reuse_raptor:
|
||||||
raptor_perf = skipped_perf("Skipped by --skip-pimsim-nn")
|
raptor_perf = skipped_perf("Skipped by --skip-pimsim-nn")
|
||||||
pimcomp_perf = skipped_perf("Skipped by --skip-pimsim-nn")
|
if not reuse_pimcomp:
|
||||||
|
pimcomp_perf = skipped_perf("Skipped by --skip-pimsim-nn")
|
||||||
elif pimsim_config is None:
|
elif pimsim_config is None:
|
||||||
if not reuse_raptor:
|
if not reuse_raptor:
|
||||||
raptor_perf = skipped_perf("pimsim-nn config is not available")
|
raptor_perf = skipped_perf("pimsim-nn config is not available")
|
||||||
pimcomp_perf = skipped_perf("pimsim-nn config is not available")
|
if not reuse_pimcomp:
|
||||||
|
pimcomp_perf = skipped_perf("pimsim-nn config is not available")
|
||||||
else:
|
else:
|
||||||
if not reuse_raptor and raptor_pim_dir is not None:
|
if not reuse_raptor and raptor_pim_dir is not None:
|
||||||
raptor_pimsim_dir = try_stage(
|
raptor_pimsim_dir = try_stage(
|
||||||
@@ -1645,30 +1694,31 @@ def main():
|
|||||||
elif not reuse_raptor:
|
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 and simulation_info is not None:
|
if not reuse_pimcomp:
|
||||||
pimcomp_pimsim_dir = try_stage(
|
if simulation_info is not None:
|
||||||
failures,
|
pimcomp_pimsim_dir = try_stage(
|
||||||
"Export PIMCOMP for pimsim-nn",
|
|
||||||
export_pimcomp_for_pimsim_nn,
|
|
||||||
simulation_info,
|
|
||||||
out_dir / "pimcomp/pimsim_nn",
|
|
||||||
)
|
|
||||||
if pimcomp_pimsim_dir is not None:
|
|
||||||
perf = try_stage(
|
|
||||||
failures,
|
failures,
|
||||||
"Non-Functional Simulation PIMCOMP",
|
"Export PIMCOMP for pimsim-nn",
|
||||||
run_pimsim_nn,
|
export_pimcomp_for_pimsim_nn,
|
||||||
"Non-Functional Simulation PIMCOMP",
|
simulation_info,
|
||||||
pimcomp_pimsim_dir,
|
out_dir / "pimcomp/pimsim_nn",
|
||||||
pimsim_config,
|
|
||||||
steps,
|
|
||||||
args,
|
|
||||||
)
|
)
|
||||||
pimcomp_perf = perf if perf is not None else failed_perf("pimsim-nn PIMCOMP failed")
|
if pimcomp_pimsim_dir is not None:
|
||||||
|
perf = try_stage(
|
||||||
|
failures,
|
||||||
|
"Non-Functional Simulation PIMCOMP",
|
||||||
|
run_pimsim_nn,
|
||||||
|
"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")
|
||||||
|
else:
|
||||||
|
pimcomp_perf = failed_perf("PIMCOMP pimsim-nn export failed")
|
||||||
else:
|
else:
|
||||||
pimcomp_perf = failed_perf("PIMCOMP pimsim-nn export failed")
|
pimcomp_perf = skipped_perf("PIMCOMP SimulationInfo.gz is not available")
|
||||||
else:
|
|
||||||
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():
|
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)
|
parsed = try_stage(failures, "Parse Raptor instructions", parse_raptor_instructions, raptor_pim_dir)
|
||||||
@@ -1676,11 +1726,12 @@ def main():
|
|||||||
elif not reuse_raptor:
|
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 and simulation_info is not None and simulation_info.exists():
|
if not reuse_pimcomp:
|
||||||
parsed = try_stage(failures, "Parse PIMCOMP instructions", parse_pimcomp_instructions, simulation_info)
|
if simulation_info is not None and simulation_info.exists():
|
||||||
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)
|
||||||
else:
|
pimcomp_instr = parsed if parsed is not None else empty_instruction_summary(error="Failed to parse PIMCOMP instructions")
|
||||||
pimcomp_instr = empty_instruction_summary("PIMCOMP SimulationInfo.gz is not available")
|
else:
|
||||||
|
pimcomp_instr = empty_instruction_summary("PIMCOMP SimulationInfo.gz is not available")
|
||||||
|
|
||||||
report_path = out_dir / "pimcomp/comparison_report.md"
|
report_path = out_dir / "pimcomp/comparison_report.md"
|
||||||
write_report(
|
write_report(
|
||||||
@@ -1712,6 +1763,7 @@ def main():
|
|||||||
"pimcomp_config": str(args.pimcomp_config),
|
"pimcomp_config": str(args.pimcomp_config),
|
||||||
"raptor_extra_args": args.raptor_extra_arg,
|
"raptor_extra_args": args.raptor_extra_arg,
|
||||||
"reused_raptor_report": optional_path(args.reuse_raptor_report.resolve()) if reuse_raptor else None,
|
"reused_raptor_report": optional_path(args.reuse_raptor_report.resolve()) if reuse_raptor else None,
|
||||||
|
"reused_pimcomp_report": optional_path(reused_pimcomp_report_path) if reuse_pimcomp else None,
|
||||||
"failures": failures,
|
"failures": failures,
|
||||||
"steps": [asdict(step) for step in steps],
|
"steps": [asdict(step) for step in steps],
|
||||||
"raptor_validation": asdict(raptor_validation),
|
"raptor_validation": asdict(raptor_validation),
|
||||||
@@ -1730,6 +1782,7 @@ def main():
|
|||||||
"raptor_pimsim_nn": optional_path(raptor_pimsim_dir),
|
"raptor_pimsim_nn": optional_path(raptor_pimsim_dir),
|
||||||
"pimcomp_simulation_info": optional_path(simulation_info),
|
"pimcomp_simulation_info": optional_path(simulation_info),
|
||||||
"pimcomp_exported_pim": optional_path(pimcomp_export_dir),
|
"pimcomp_exported_pim": optional_path(pimcomp_export_dir),
|
||||||
|
"pimcomp_pimsim_nn": optional_path(pimcomp_pimsim_dir),
|
||||||
"pimsim_config": optional_path(pimsim_config),
|
"pimsim_config": optional_path(pimsim_config),
|
||||||
"report_markdown": str(report_path),
|
"report_markdown": str(report_path),
|
||||||
},
|
},
|
||||||
@@ -1739,15 +1792,28 @@ def main():
|
|||||||
json.dump(json_report, f, indent=2)
|
json.dump(json_report, f, indent=2)
|
||||||
f.write("\n")
|
f.write("\n")
|
||||||
|
|
||||||
failed_steps = any(step.status != "passed" for step in steps)
|
partial_side = "pimcomp" if reuse_raptor else "raptor" if args.reuse_pimcomp_report else None
|
||||||
|
other_side = "RAPTOR" if partial_side == "pimcomp" else "PIMCOMP"
|
||||||
|
relevant_failures = (
|
||||||
|
failures if partial_side is None
|
||||||
|
else [failure for failure in failures if other_side not in failure["stage"].upper()]
|
||||||
|
)
|
||||||
|
relevant_steps = (
|
||||||
|
steps if partial_side is None
|
||||||
|
else [step for step in steps if other_side not in step.name.upper()]
|
||||||
|
)
|
||||||
|
relevant_validations = (
|
||||||
|
(raptor_validation, pimcomp_validation)
|
||||||
|
if partial_side is None
|
||||||
|
else (raptor_validation,) if partial_side == "raptor" else (pimcomp_validation,)
|
||||||
|
)
|
||||||
|
failed_steps = any(step.status != "passed" for step in relevant_steps)
|
||||||
functional_failure = any(
|
functional_failure = any(
|
||||||
result.status == "done" and not result.passed
|
result.status == "done" and not result.passed
|
||||||
for result in (raptor_validation, pimcomp_validation)
|
for result in relevant_validations
|
||||||
)
|
)
|
||||||
failed = bool(failures or failed_steps or functional_failure)
|
failed = bool(relevant_failures or failed_steps or functional_failure)
|
||||||
result = "FAIL" if args.fail_on_error and failed else "DONE" if failed else "PASS"
|
print("\n" + Style.BRIGHT + Fore.GREEN + "[Completed]" + Style.RESET_ALL)
|
||||||
color = Fore.RED if result == "FAIL" else Fore.YELLOW if result == "DONE" else Fore.GREEN
|
|
||||||
print("\n" + Style.BRIGHT + f"Result: {color}{result}" + Style.RESET_ALL)
|
|
||||||
print(f" Report: {report_path}")
|
print(f" Report: {report_path}")
|
||||||
print(f" JSON: {json_path}")
|
print(f" JSON: {json_path}")
|
||||||
if failures or failed_steps:
|
if failures or failed_steps:
|
||||||
|
|||||||
@@ -101,8 +101,8 @@ def write_results_csv(
|
|||||||
) -> Path:
|
) -> Path:
|
||||||
output = (root or SUITE) / "results.csv"
|
output = (root or SUITE) / "results.csv"
|
||||||
fields = (
|
fields = (
|
||||||
"model",
|
|
||||||
"arch",
|
"arch",
|
||||||
|
"model",
|
||||||
"mode",
|
"mode",
|
||||||
"raptor_pipeline",
|
"raptor_pipeline",
|
||||||
"pimcomp_pipeline",
|
"pimcomp_pipeline",
|
||||||
@@ -185,12 +185,26 @@ def write_results_csv(
|
|||||||
pimcomp_energy_pj=format_value(pimcomp_values["energy"]),
|
pimcomp_energy_pj=format_value(pimcomp_values["energy"]),
|
||||||
)
|
)
|
||||||
if raptor_metric is not None and pimcomp_metric is not None:
|
if raptor_metric is not None and pimcomp_metric is not None:
|
||||||
|
min_metric = min(raptor_metric, pimcomp_metric)
|
||||||
row.update(
|
row.update(
|
||||||
better_compiler=comparison_winner(comparison_mode, raptor_metric, pimcomp_metric),
|
better_compiler=comparison_winner(comparison_mode, raptor_metric, pimcomp_metric),
|
||||||
speedup=f"{max(raptor_metric, pimcomp_metric) / min(raptor_metric, pimcomp_metric):.2f}",
|
speedup=(
|
||||||
|
f"{max(raptor_metric, pimcomp_metric) / min_metric:.2f}"
|
||||||
|
if min_metric > 0
|
||||||
|
else "NA"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
rows.sort(key=lambda row: (list(MODELS).index(row["model"]), row["mode"], int(row["raptor_pipeline"])))
|
model_order = {name: index for index, name in enumerate(MODELS)}
|
||||||
|
mode_order = {"latency": 0, "throughput": 1}
|
||||||
|
rows.sort(
|
||||||
|
key=lambda row: (
|
||||||
|
row["arch"],
|
||||||
|
model_order[row["model"]],
|
||||||
|
mode_order[row["mode"]],
|
||||||
|
int(row["raptor_pipeline"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
with open(output, "w", newline="", encoding="utf-8") as f:
|
with open(output, "w", newline="", encoding="utf-8") as f:
|
||||||
writer = csv.DictWriter(f, fieldnames=fields, lineterminator="\n")
|
writer = csv.DictWriter(f, fieldnames=fields, lineterminator="\n")
|
||||||
writer.writeheader()
|
writer.writeheader()
|
||||||
@@ -213,15 +227,20 @@ def performance_values(performance: dict) -> dict[str, float | None]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def comparison_passed(report: dict) -> bool:
|
def comparison_passed(report: dict, compiler: str | None = None) -> bool:
|
||||||
if report.get("failures"):
|
other_compiler = "PIMCOMP" if compiler == "raptor" else "RAPTOR"
|
||||||
|
if any(
|
||||||
|
compiler is None or other_compiler not in failure.get("stage", "").upper()
|
||||||
|
for failure in report.get("failures", [])
|
||||||
|
):
|
||||||
return False
|
return False
|
||||||
for key in ("raptor_validation", "pimcomp_validation"):
|
compilers = (compiler,) if compiler is not None else ("raptor", "pimcomp")
|
||||||
result = report.get(key) or {}
|
for name in compilers:
|
||||||
|
result = report.get(f"{name}_validation") or {}
|
||||||
if result.get("status") != "done" or not result.get("passed"):
|
if result.get("status") != "done" or not result.get("passed"):
|
||||||
return False
|
return False
|
||||||
for key in ("raptor_performance", "pimcomp_performance"):
|
for name in compilers:
|
||||||
performance = report.get(key) or {}
|
performance = report.get(f"{name}_performance") or {}
|
||||||
if performance.get("error") or performance.get("skipped"):
|
if performance.get("error") or performance.get("skipped"):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -249,6 +268,13 @@ def print_stage(title: str, color: str) -> None:
|
|||||||
print("\n" + Style.BRIGHT + color + f"[{title}]" + Style.RESET_ALL, flush=True)
|
print("\n" + 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)
|
||||||
|
if output:
|
||||||
|
print(output, end="" if output.endswith("\n") else "\n")
|
||||||
|
print("=" * 72, flush=True)
|
||||||
|
|
||||||
|
|
||||||
def run(command: list[str], *, dry_run: bool, check: bool = True) -> int:
|
def run(command: list[str], *, dry_run: bool, check: bool = True) -> int:
|
||||||
print(f" cwd: {REPO}", flush=True)
|
print(f" cwd: {REPO}", flush=True)
|
||||||
print(f" $ {shlex.join(command)}", flush=True)
|
print(f" $ {shlex.join(command)}", flush=True)
|
||||||
@@ -277,7 +303,7 @@ def run_comparison_job(job: tuple[str, list[str], Path | None]) -> tuple[str, in
|
|||||||
os.dup2(saved_stderr, 2)
|
os.dup2(saved_stderr, 2)
|
||||||
os.close(saved_stdout)
|
os.close(saved_stdout)
|
||||||
os.close(saved_stderr)
|
os.close(saved_stderr)
|
||||||
return label, returncode, str(log_path)
|
return label, returncode, log_path.read_text(encoding="utf-8", errors="replace")
|
||||||
|
|
||||||
|
|
||||||
def validate_pimcomp_source() -> None:
|
def validate_pimcomp_source() -> None:
|
||||||
@@ -300,6 +326,7 @@ def comparison_command(
|
|||||||
timeout: float,
|
timeout: float,
|
||||||
reuse_raptor_report: Path | None = None,
|
reuse_raptor_report: Path | None = None,
|
||||||
reuse_pimcomp_dir: Path | None = None,
|
reuse_pimcomp_dir: Path | None = None,
|
||||||
|
reuse_pimcomp_report: Path | None = None,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
time_args = ["--pimsim-time-ms", str(pimsim_time_ms)] if mode == "throughput" else []
|
time_args = ["--pimsim-time-ms", str(pimsim_time_ms)] if mode == "throughput" else []
|
||||||
reuse_args = []
|
reuse_args = []
|
||||||
@@ -307,6 +334,8 @@ def comparison_command(
|
|||||||
reuse_args.extend(["--reuse-raptor-report", str(reuse_raptor_report)])
|
reuse_args.extend(["--reuse-raptor-report", str(reuse_raptor_report)])
|
||||||
if reuse_pimcomp_dir is not None:
|
if reuse_pimcomp_dir is not None:
|
||||||
reuse_args.extend(["--reuse-pimcomp-dir", str(reuse_pimcomp_dir)])
|
reuse_args.extend(["--reuse-pimcomp-dir", str(reuse_pimcomp_dir)])
|
||||||
|
if reuse_pimcomp_report is not None:
|
||||||
|
reuse_args.extend(["--reuse-pimcomp-report", str(reuse_pimcomp_report)])
|
||||||
return [
|
return [
|
||||||
sys.executable,
|
sys.executable,
|
||||||
str(COMPARE),
|
str(COMPARE),
|
||||||
@@ -357,11 +386,7 @@ def comparison_command_for(
|
|||||||
*,
|
*,
|
||||||
reuse_shared_pimcomp: bool,
|
reuse_shared_pimcomp: bool,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
reuse_pimcomp_dir = None
|
report = spec.output_dir / "pimcomp/comparison_report.json"
|
||||||
if reuse_shared_pimcomp:
|
|
||||||
reuse_pimcomp_dir = spec.shared_pimcomp_dir
|
|
||||||
elif args.only == "raptor":
|
|
||||||
reuse_pimcomp_dir = existing_pimcomp_dir(spec.output_dir)
|
|
||||||
return comparison_command(
|
return comparison_command(
|
||||||
spec.model,
|
spec.model,
|
||||||
spec.output_dir,
|
spec.output_dir,
|
||||||
@@ -373,11 +398,12 @@ def comparison_command_for(
|
|||||||
args.pimsim_time_ms,
|
args.pimsim_time_ms,
|
||||||
args.timeout_seconds,
|
args.timeout_seconds,
|
||||||
reuse_raptor_report=(
|
reuse_raptor_report=(
|
||||||
spec.output_dir / "pimcomp/comparison_report.json"
|
report
|
||||||
if args.only == "pimcomp"
|
if args.only == "pimcomp"
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
reuse_pimcomp_dir=reuse_pimcomp_dir,
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -388,11 +414,6 @@ def pimcomp_artifact_ready(output_dir: Path) -> bool:
|
|||||||
) and (output_dir.parent / "comparison_report.json").is_file()
|
) and (output_dir.parent / "comparison_report.json").is_file()
|
||||||
|
|
||||||
|
|
||||||
def existing_pimcomp_dir(comparison_dir: Path) -> Path | None:
|
|
||||||
output_dir = comparison_dir / "pimcomp/output"
|
|
||||||
return output_dir if pimcomp_artifact_ready(output_dir) else None
|
|
||||||
|
|
||||||
|
|
||||||
def run_comparison_jobs(
|
def run_comparison_jobs(
|
||||||
comparison_jobs: list[tuple[str, list[str], Path | None]],
|
comparison_jobs: list[tuple[str, list[str], Path | None]],
|
||||||
max_workers: int,
|
max_workers: int,
|
||||||
@@ -420,11 +441,8 @@ def run_comparison_jobs(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
failed = []
|
failed = []
|
||||||
for label, returncode, result_log in completed:
|
for label, returncode, output in completed:
|
||||||
if result_log:
|
print_completed(label, output or "")
|
||||||
output = Path(result_log).read_text(encoding="utf-8", errors="replace")
|
|
||||||
if output:
|
|
||||||
print(output, end="" if output.endswith("\n") else "\n")
|
|
||||||
if returncode:
|
if returncode:
|
||||||
failed.append(label)
|
failed.append(label)
|
||||||
return failed, log_offset + len(jobs)
|
return failed, log_offset + len(jobs)
|
||||||
@@ -456,13 +474,6 @@ def config_path(arch: str, mode: str, sim_time_ms: int, *, write: bool) -> Path:
|
|||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def core_count(config: Path) -> int:
|
|
||||||
if not config.exists() and config.name.startswith("throughput_config_"):
|
|
||||||
config = config.with_name("throughput_config_1000ms.json")
|
|
||||||
with open(config, encoding="utf-8") as f:
|
|
||||||
return int(json.load(f)["chip_config"]["core_cnt"])
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(
|
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."
|
||||||
@@ -490,7 +501,7 @@ def main() -> int:
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--only",
|
"--only",
|
||||||
choices=("raptor", "pimcomp"),
|
choices=("raptor", "pimcomp"),
|
||||||
help="Re-run only this compiler and reuse the other side's existing artifacts.",
|
help="Re-run only this compiler's compile, validation, and simulation stages; preserve the other side from its report.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--pipeline",
|
"--pipeline",
|
||||||
@@ -552,22 +563,6 @@ def main() -> int:
|
|||||||
mode: config_path(arch, mode, args.pimsim_time_ms, write=not args.dry_run)
|
mode: config_path(arch, mode, args.pimsim_time_ms, write=not args.dry_run)
|
||||||
for mode, _, _ in comparisons
|
for mode, _, _ in comparisons
|
||||||
}
|
}
|
||||||
unsupported = [
|
|
||||||
pipeline for mode, pipeline, _ in comparisons
|
|
||||||
if mode == "throughput" and core_count(configs[mode]) % pipeline
|
|
||||||
]
|
|
||||||
if unsupported:
|
|
||||||
if args.pipeline is not None:
|
|
||||||
parser.error(
|
|
||||||
f"{arch} has {core_count(configs['throughput'])} cores; "
|
|
||||||
f"throughput pipelines must divide that count (invalid: {unsupported})"
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
Fore.YELLOW
|
|
||||||
+ f"Skipping unsupported {arch} throughput pipeline(s): {unsupported}"
|
|
||||||
+ Style.RESET_ALL
|
|
||||||
)
|
|
||||||
comparisons = tuple(comparison for comparison in comparisons if comparison[1] not in unsupported)
|
|
||||||
comparisons_by_arch[arch] = comparisons
|
comparisons_by_arch[arch] = comparisons
|
||||||
configs_by_arch[arch] = configs
|
configs_by_arch[arch] = configs
|
||||||
|
|
||||||
@@ -581,19 +576,14 @@ def main() -> int:
|
|||||||
for name in args.models:
|
for name in args.models:
|
||||||
for mode, pipeline, _ in comparisons_by_arch[arch]:
|
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)
|
||||||
required = (
|
required = comparison_dir / "pimcomp/comparison_report.json"
|
||||||
comparison_dir / "pimcomp/comparison_report.json"
|
if not required.exists():
|
||||||
if args.only == "pimcomp"
|
missing_reuse.append(str(required))
|
||||||
else existing_pimcomp_dir(comparison_dir)
|
|
||||||
)
|
|
||||||
if required is None or not required.exists():
|
|
||||||
missing_reuse.append(str(required or comparison_dir / "pimcomp"))
|
|
||||||
elif args.only == "raptor":
|
elif args.only == "raptor":
|
||||||
report = comparison_dir / "pimcomp/comparison_report.json"
|
if json.loads(required.read_text(encoding="utf-8")).get(
|
||||||
if not report.exists() or json.loads(report.read_text(encoding="utf-8")).get(
|
|
||||||
"pimcomp_model_source"
|
"pimcomp_model_source"
|
||||||
) != "original_onnx":
|
) != "original_onnx":
|
||||||
missing_reuse.append(f"{report} (not generated from the original ONNX model)")
|
missing_reuse.append(f"{required} (not generated from the original ONNX model)")
|
||||||
if missing_reuse:
|
if missing_reuse:
|
||||||
parser.error(
|
parser.error(
|
||||||
f"--only {args.only} needs existing comparison artifacts: "
|
f"--only {args.only} needs existing comparison artifacts: "
|
||||||
@@ -609,19 +599,26 @@ def main() -> int:
|
|||||||
print(f"Modes: {', '.join(args.mode)}")
|
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"Max parallel jobs: {args.jobs}")
|
||||||
|
print(
|
||||||
|
f"Comparison jobs: "
|
||||||
|
f"{sum(len(args.models) * len(comparisons) for comparisons in comparisons_by_arch.values())}"
|
||||||
|
)
|
||||||
print(f"Results root: {out_dir or SUITE}")
|
print(f"Results root: {out_dir or SUITE}")
|
||||||
print("=" * 72)
|
print("=" * 72)
|
||||||
|
|
||||||
print_stage("Prepare shared artifacts", STAGE_COLORS["Build Runner"])
|
print_stage("Prepare shared artifacts", STAGE_COLORS["Build Runner"])
|
||||||
for name in args.models:
|
for name in args.models:
|
||||||
run(
|
try:
|
||||||
prepare_common_command(
|
run(
|
||||||
MODELS[name],
|
prepare_common_command(
|
||||||
model_dir(out_dir, name) / "common",
|
MODELS[name],
|
||||||
args.timeout_seconds,
|
model_dir(out_dir, name) / "common",
|
||||||
),
|
args.timeout_seconds,
|
||||||
dry_run=args.dry_run,
|
),
|
||||||
)
|
dry_run=args.dry_run,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
print_completed(f"shared artifacts: {name}")
|
||||||
|
|
||||||
failed = []
|
failed = []
|
||||||
comparison_specs: list[ComparisonSpec] = []
|
comparison_specs: list[ComparisonSpec] = []
|
||||||
@@ -629,7 +626,7 @@ def main() -> int:
|
|||||||
for arch in args.arch:
|
for arch in args.arch:
|
||||||
comparisons = comparisons_by_arch[arch]
|
comparisons = comparisons_by_arch[arch]
|
||||||
configs = configs_by_arch[arch]
|
configs = configs_by_arch[arch]
|
||||||
for index, name in enumerate(args.models, start=1):
|
for name in args.models:
|
||||||
for mode, pipeline, pimcomp_pipeline in comparisons:
|
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)
|
||||||
label = f"{arch}/{name}/{mode}/pipeline{pipeline}"
|
label = f"{arch}/{name}/{mode}/pipeline{pipeline}"
|
||||||
@@ -639,11 +636,6 @@ def main() -> int:
|
|||||||
if anchor:
|
if anchor:
|
||||||
shared_pimcomp_dir = model_result_dir / "pimcomp/output"
|
shared_pimcomp_dir = model_result_dir / "pimcomp/output"
|
||||||
shared_pimcomp_by_group[group] = shared_pimcomp_dir
|
shared_pimcomp_by_group[group] = shared_pimcomp_dir
|
||||||
print(
|
|
||||||
"\n" + Fore.CYAN + f"[{arch} {index}/{len(args.models)}]" + Style.RESET_ALL
|
|
||||||
+ f" {Style.BRIGHT}Comparing {name} ({mode}, pipeline={pipeline}){Style.RESET_ALL}",
|
|
||||||
flush=True,
|
|
||||||
)
|
|
||||||
comparison_specs.append(
|
comparison_specs.append(
|
||||||
ComparisonSpec(
|
ComparisonSpec(
|
||||||
label=label,
|
label=label,
|
||||||
@@ -669,17 +661,20 @@ def main() -> int:
|
|||||||
if run(command, dry_run=True, check=False):
|
if run(command, dry_run=True, check=False):
|
||||||
failed.append(spec.label)
|
failed.append(spec.label)
|
||||||
elif comparison_specs:
|
elif comparison_specs:
|
||||||
print(f"Running {len(comparison_specs)} comparison(s)")
|
anchor_specs = comparison_specs if args.only == "raptor" else [
|
||||||
|
spec for spec in comparison_specs if spec.anchor
|
||||||
|
]
|
||||||
anchor_jobs = [
|
anchor_jobs = [
|
||||||
(
|
(
|
||||||
spec.label,
|
spec.label,
|
||||||
comparison_command_for(spec, args, reuse_shared_pimcomp=False),
|
comparison_command_for(spec, args, reuse_shared_pimcomp=False),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
for spec in comparison_specs
|
for spec in anchor_specs
|
||||||
if spec.anchor
|
]
|
||||||
|
dependent_specs = [] if args.only == "raptor" else [
|
||||||
|
spec for spec in comparison_specs if not spec.anchor
|
||||||
]
|
]
|
||||||
dependent_specs = [spec for spec in comparison_specs if not spec.anchor]
|
|
||||||
print_directly = min(args.jobs, len(comparison_specs)) == 1
|
print_directly = min(args.jobs, len(comparison_specs)) == 1
|
||||||
with (nullcontext(None) if print_directly else TemporaryDirectory(prefix="raptor-pimcomp-")) as log_dir:
|
with (nullcontext(None) if print_directly else TemporaryDirectory(prefix="raptor-pimcomp-")) as log_dir:
|
||||||
anchor_failed, log_offset = run_comparison_jobs(
|
anchor_failed, log_offset = run_comparison_jobs(
|
||||||
@@ -729,7 +724,8 @@ def main() -> int:
|
|||||||
label = f"{arch}/{name}/{mode}/pipeline{pipeline}"
|
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) / "pimcomp/comparison_report.json"
|
||||||
if not report_path.exists() or not comparison_passed(
|
if not report_path.exists() or not comparison_passed(
|
||||||
json.loads(report_path.read_text(encoding="utf-8"))
|
json.loads(report_path.read_text(encoding="utf-8")),
|
||||||
|
args.only,
|
||||||
):
|
):
|
||||||
if label not in failed:
|
if label not in failed:
|
||||||
failed.append(label)
|
failed.append(label)
|
||||||
|
|||||||
Reference in New Issue
Block a user