import argparse
import json
import pathlib
import statistics
import subprocess
import sys
HERE = pathlib.Path(__file__).resolve().parent.parent
DEFAULT_GPU = HERE / "target/release/examples/gpu_load"
DEFAULT_NPU = pathlib.Path.home() / "code/dinov3-xdna2/target/release/npu_load"
DEFAULT_NPU_BUILD = pathlib.Path.home() / "code/dinov3-xdna2/build"
def gpu_command(options):
return [
str(options.gpu),
"--seconds", str(options.seconds),
"--mib", str(options.mib),
"--mode", options.mode,
]
def npu_command(options):
return [
str(options.npu), str(options.npu_build),
"--seconds", str(options.seconds),
"--m", str(options.m),
]
def launch(command):
return subprocess.Popen(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
def collect(process, label):
out, err = process.communicate()
if process.returncode != 0:
sys.exit(f"{label} failed (exit {process.returncode}):\n{err.strip()}")
line = next((l for l in out.splitlines() if l.startswith("{")), None)
if line is None:
sys.exit(f"{label} printed no JSON record:\n{out.strip()}\n{err.strip()}")
return json.loads(line)
def run_alone(command, label):
return collect(launch(command), label)
def run_together(gpu, npu):
gpu_process = launch(gpu)
npu_process = launch(npu)
return collect(gpu_process, "gpu_load"), collect(npu_process, "npu_load")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--seconds", type=float, default=10.0)
parser.add_argument("--repeat", type=int, default=3, help="trials per configuration")
parser.add_argument("--mib", type=int, default=256, help="GPU buffer size")
parser.add_argument("--mode", default="fill", choices=("fill", "copy"))
parser.add_argument("--m", type=int, default=16384, help="NPU GEMM rows")
parser.add_argument("--gpu", type=pathlib.Path, default=DEFAULT_GPU)
parser.add_argument("--npu", type=pathlib.Path, default=DEFAULT_NPU)
parser.add_argument("--npu-build", type=pathlib.Path, default=DEFAULT_NPU_BUILD)
parser.add_argument("--json", type=pathlib.Path, help="write the raw records here")
options = parser.parse_args()
for binary in (options.gpu, options.npu):
if not binary.exists():
sys.exit(f"missing load generator: {binary}")
gpu, npu = gpu_command(options), npu_command(options)
records = {"alone": {"gpu": [], "npu": []}, "together": {"gpu": [], "npu": []}}
for trial in range(options.repeat):
print(f"trial {trial + 1}/{options.repeat}", file=sys.stderr)
records["alone"]["gpu"].append(run_alone(gpu, "gpu_load"))
records["alone"]["npu"].append(run_alone(npu, "npu_load"))
together_gpu, together_npu = run_together(gpu, npu)
records["together"]["gpu"].append(together_gpu)
records["together"]["npu"].append(together_npu)
def median(kind, device, field):
return statistics.median(r[field] for r in records[kind][device])
gpu_alone = median("alone", "gpu", "gb_s_mean")
gpu_together = median("together", "gpu", "gb_s_mean")
npu_alone = median("alone", "npu", "dispatch_s")
npu_together = median("together", "npu", "dispatch_s")
npu_bw_alone = median("alone", "npu", "gb_s")
npu_bw_together = median("together", "npu", "gb_s")
gpu_ratio = gpu_together / gpu_alone
npu_ratio = npu_together / npu_alone
efficiency = gpu_ratio + npu_ratio
print()
print(f"GPU load: {options.mode} over {options.mib} MiB")
print(f"NPU load: bfp16 GEMM m={options.m} k=4096 n=1024")
print(f"{options.repeat} trials of {options.seconds:g}s each\n")
row = "{:<26} {:>12} {:>12} {:>9}"
print(row.format("metric", "alone", "concurrent", "retained"))
print("-" * 62)
print(row.format(
"GPU bandwidth (GB/s)", f"{gpu_alone:.1f}", f"{gpu_together:.1f}",
f"{gpu_ratio:.1%}",
))
print(row.format(
"NPU dispatch (per s)", f"{npu_alone:.1f}", f"{npu_together:.1f}",
f"{npu_ratio:.1%}",
))
print(row.format(
"NPU bandwidth (GB/s)", f"{npu_bw_alone:.1f}", f"{npu_bw_together:.1f}", "",
))
print("-" * 62)
print(f"{'concurrency efficiency':<26} {efficiency:>12.2f} (1.00 = zero-sum, 2.00 = independent)")
print(f"{'aggregate GB/s':<26} {gpu_alone + npu_bw_alone:>12.1f} {gpu_together + npu_bw_together:>12.1f}")
if options.json:
options.json.write_text(json.dumps(records, indent=2))
print(f"\nraw records written to {options.json}")
if __name__ == "__main__":
main()