diff --git a/.gitignore b/.gitignore index 79ca34de5..dea7c4ae0 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ package-lock.json /outputs/**/* /models/**/* /tmp +/benchmarks .*/ cache tunableop_results*.csv diff --git a/cli/sdnq-attention-benchmark.py b/cli/sdnq-attention-benchmark.py index ea88a1ed0..aa36db766 100755 --- a/cli/sdnq-attention-benchmark.py +++ b/cli/sdnq-attention-benchmark.py @@ -289,8 +289,9 @@ def parse_cli(): parser.add_argument("--skip-bench", action="store_true", help="skip benchmarks, run checks and the fp8 and compile probes only") parser.add_argument("--dtype", type=str, default="auto", choices=["auto", "bf16", "fp16"], help="tensor dtype for benchmarks; auto uses the dtype the webui selected for this gpu (default: %(default)s)") parser.add_argument("--config-timeout", type=int, default=300, help="best effort: abort a config whose compile plus first call exceeds this many seconds, 0 disables; cannot interrupt native-level hangs (default: %(default)s)") - parser.add_argument("--save", type=str, default=None, help="write a plain-text copy of all tables and notes to this file; keeps colors and live progress on the terminal, unlike piping through tee") - parser.add_argument("--json", type=str, default=None, help="write structured results (environment, probes, per-shape and dequant timings, recommendations) to this file") + parser.add_argument("--save", type=str, default="auto", help="plain-text copy of all tables and notes; 'auto' (default) names it -t-.txt in the output directory, 'none' disables, anything else is used as the path") + parser.add_argument("--json", type=str, default="auto", help="structured results (environment, probes, per-shape and dequant timings, recommendations); 'auto' (default) names it -t-.json in the output directory, 'none' disables, anything else is used as the path") + parser.add_argument("--outdir", type=str, default=None, help="directory for auto-named outputs (default: $SDNQ_BENCH_DIR, or benchmarks/ under the sdnext root)") args = parser.parse_args() sys.argv = sys.argv[:1] # sdnext parses argv again on import and rejects unknown arguments return args @@ -868,6 +869,36 @@ def print_environment(fp8_result, prep_status, prep_detail, weight_dequant_resul ) +def resolve_output_paths(args): + # auto outputs follow the archive convention -t-, one + # stem shared by the txt/json pair, suffixed -2, -3... when the pair already exists + args.save = None if str(args.save).strip().lower() in {"none", ""} else args.save + args.json = None if str(args.json).strip().lower() in {"none", ""} else args.json + if args.save != "auto" and args.json != "auto": + return + try: + gpu = torch_device_module.get_device_name(torch.device(torch_device)) + except Exception: + gpu = "gpu" + drop = {"nvidia", "geforce", "rtx", "gtx", "amd", "radeon", "rx", "intel", "arc", "graphics", "apple", "laptop", "gpu"} + tokens = ["".join(ch for ch in token if ch.isalnum()) for token in gpu.lower().replace("(r)", " ").replace("(tm)", " ").split()] + tokens = [token for token in tokens if token and token not in drop] + gpu_id = "".join(tokens) or "gpu" + torch_digits = "".join(str(torch.__version__).split("+", maxsplit=1)[0].split(".")[:2]) + stem = f"{gpu_id}-t{torch_digits}-{time.strftime('%b%d').lower()}" + outdir = args.outdir or os.environ.get("SDNQ_BENCH_DIR", None) or os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "benchmarks") + outdir = os.path.expanduser(outdir) + os.makedirs(outdir, exist_ok=True) + candidate, counter = stem, 1 + while os.path.exists(os.path.join(outdir, f"{candidate}.txt")) or os.path.exists(os.path.join(outdir, f"{candidate}.json")): + counter += 1 + candidate = f"{stem}-{counter}" + if args.save == "auto": + args.save = os.path.join(outdir, f"{candidate}.txt") + if args.json == "auto": + args.json = os.path.join(outdir, f"{candidate}.json") + + def print_banner(selected, sections, args): lines = [ "measures sdnq attention and weight dequantization speed and accuracy on this gpu and recommends values for [cyan]Compute Settings -> SDNQ / SDNQ Attention[/cyan]", @@ -3516,6 +3547,7 @@ def main(): if unknown: console.print(f"[red]unknown shape preset(s): {', '.join(unknown)}; available: {', '.join(shape_presets)}[/red]") sys.exit(1) + resolve_output_paths(args) global pending_outputs # pylint: disable=global-statement pending_outputs = dict(save=args.save, json=args.json, args=args, sections=sections, selected=selected) print_banner(selected, sections, args)