import argparse from collections import Counter, defaultdict, deque import json import os from pathlib import Path import struct import subprocess import sys import tempfile from types import SimpleNamespace REPO = Path(__file__).resolve().parents[2] sys.path.insert(0, str(REPO / "validation/tools/pim/pimcomp/correctness/synchronization")) import global_memory as sync from raptor_validation.pimsim_nn import export_raptor_pimsim_artifact def read_events(artifact): streams = {} for path in artifact.glob("core_*.pim"): core = int(path.stem.split("_")[1]) registers, events = {}, [] data = path.read_bytes() assert data[:4] == b"PIMB" for pc, (op, rd, rs, flags, arg, selection, offset, size) in enumerate( struct.iter_unpack(" 1 if len({reader[0] for reader in readers}) > 1: stalled_reader = readers[-1] for reader in readers: assert reaches(writer, reader), ("missing READY", writer, reader) if iteration < 2: assert reaches(reader, stores[iteration + 1, address, size]), ( "unsafe overwrite", reader, address) assert shared, "fixture must exercise a publication with multiple physical readers" return shared, stalled_reader def check_periodic_estimate(report, streams): assert report["diagnostic_only"] and report["status"] == "estimated", report assert 0 <= report["recurrence_upper_ns"] - report["recurrence_lower_ns"] <= 1 assert report["critical_cycle_distance"] > 0 ratio = report["critical_cycle_delay_ns"] / report["critical_cycle_distance"] assert report["recurrence_lower_ns"] <= ratio <= report["recurrence_upper_ns"] signals, waits, memory = Counter(), {}, Counter() # The oracle exporter converts this fp32 fixture's addresses to int8 bytes. host_bytes = report["native_host_buffer_bytes"] // 4 for core, events in streams.items(): for _, op, *args in events: if op == 32: target, register, _ = args signals[core, target, register] += 1 elif op == 31: _, register, count = args assert (core, register) not in waits waits[core, register] = count elif op in (25, 26) and args[0] < host_bytes: memory[op, "requests"] += 1 memory[op, "bytes"] += args[1] planned = Counter() for event in report["event_registers"]: key = event["core"], event["register"] assert waits.pop(key) == event["expected"] for source in event["sources"]: planned[source["core"], *key] += source["count"] assert not waits and planned == signals for op, direction in ((25, "read"), (26, "write")): for quantity in ("bytes", "requests"): assert report[f"boundary_{direction}_{quantity}"] == memory[op, quantity], ( direction, quantity, report[f"boundary_{direction}_{quantity}"], memory[op, quantity]) def run_functional(artifact, inputs, references, outputs, out, policy, seed, reader=None): out.mkdir() command = [str(sync.RUST_BINARY), "--folder", str(artifact), "--output", str(out / "output.bin"), "--dump", sync.compare.build_dump_ranges(artifact / "config.json", outputs), "--mode", "throughput", "--batch-size", "8", "--input-dir", str(inputs[0].parent), "--batch-output-dir", str(out / "iterations"), "--diagnostic-schedule-policy", policy, "--diagnostic-schedule-seed", str(seed)] if reader: command += ["--diagnostic-target-stall", f"{reader[0] + 1}:{reader[2]}:1:10000"] result = subprocess.run(command, capture_output=True, text=True, timeout=120) assert result.returncode == 0, result.stdout + result.stderr for index, reference in enumerate(references[:8]): result = sync.compare.compare_simulator_outputs( out / "iterations" / f"output_{index:06d}.bin", outputs, reference, threshold=1e-3, rtol=1e-5) assert result.passed, result def main(): parser = argparse.ArgumentParser() parser.add_argument("--compiler", required=True) args = parser.parse_args() with tempfile.TemporaryDirectory(prefix="raptor-publication-") as temporary: root = Path(temporary) model = REPO / "validation/networks/pimcomp_models/vgg8/vgg8-mnist-reconstructed.onnx" options = SimpleNamespace(timeout=120, threshold=1e-3, rtol=1e-5) samples, inputs, _, _ = sync.make_inputs(model, 8, 0, root) references = sync.make_references(model, samples, root, options) _, outputs = sync.compare.onnx_io(model) for arch, cores, weights in (("arch-a", 168, 96), ("arch-b", 138, 128)): out = root / arch out.mkdir() command = [args.compiler, str(model), "-o", str(out / "model"), "--maccel=PIM", "--EmitPimCodegen", "--crossbar-size=128", f"--crossbar-count={weights}", f"--core-count={cores}", "--pipeline=4", f"--pim-target-config={sync.CONFIG_ROOT / arch / 'throughput_config_1000ms.json'}", "--mlir-disable-threading", "--pim-periodic-diagnostics", "--pim-emit-json", "--pim-detect-communication-deadlock"] compiled = subprocess.run(command, capture_output=True, text=True, timeout=120, env=dict(os.environ, OMP_NUM_THREADS="1")) assert compiled.returncode == 0, compiled.stdout + compiled.stderr artifact = out / "pim" streams = read_events(artifact) shared, reader = check_lifetimes(streams) report = json.loads((out / "reports/pipeline_periodic.json").read_text()) oracle = out / "pimsim_nn" export_raptor_pimsim_artifact(artifact, oracle) check_periodic_estimate(report, read_events(oracle)) for policy, seed in (("greedy", 0), ("randomized", 1), ("randomized", 7)): run_functional(artifact, inputs, references, outputs, out / f"{policy}-{seed}", policy, seed) run_functional(artifact, inputs, references, outputs, out / "stalled", "randomized", 11, reader) print(f"{arch}: {shared} shared publications; three-iteration lifetime proof; eight inputs; randomized and stalled execution passed") if __name__ == "__main__": main()