diff --git a/flow/designs/asap7/gcd/autotuner-sweep.json b/flow/designs/asap7/gcd/autotuner-sweep.json new file mode 100644 index 0000000000..9fe587047f --- /dev/null +++ b/flow/designs/asap7/gcd/autotuner-sweep.json @@ -0,0 +1,10 @@ +{ + "PLACE_DENSITY_LB_ADDON": { + "type": "float", + "minmax": [ + 0.0, + 0.2 + ], + "step": 0.05 + } +} diff --git a/tools/AutoTuner/Dockerfile b/tools/AutoTuner/Dockerfile new file mode 100644 index 0000000000..25aefe3b3b --- /dev/null +++ b/tools/AutoTuner/Dockerfile @@ -0,0 +1,6 @@ +# syntax=docker/dockerfile:1 +FROM openroad/orfs:latest +WORKDIR /OpenROAD-flow-scripts/tools/AutoTuner +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt && \ + pip install --no-cache-dir --no-deps . diff --git a/tools/AutoTuner/src/autotuner/distributed.py b/tools/AutoTuner/src/autotuner/distributed.py index c5b07dc74c..baafb4e080 100644 --- a/tools/AutoTuner/src/autotuner/distributed.py +++ b/tools/AutoTuner/src/autotuner/distributed.py @@ -68,6 +68,7 @@ from uuid import uuid4 as uuid from collections import namedtuple from multiprocessing import cpu_count +import time import numpy as np import torch @@ -91,7 +92,6 @@ parse_config, read_config, read_metrics, - prepare_ray_server, CONSTRAINTS_SDC, FASTROUTE_TCL, ) @@ -101,9 +101,18 @@ # The worst of optimized metric ERROR_METRIC = 9e99 # Path to the FLOW_HOME directory -ORFS_FLOW_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), "../../../../flow") -) +if "ORFS_FLOW_DIR" in os.environ: + ORFS_FLOW_DIR = os.environ["ORFS_FLOW_DIR"] +else: + ORFS_FLOW_DIR = os.path.abspath( + os.path.join(os.path.dirname(__file__), "../../../../flow") + ) +if "INSTALL_PATH" in os.environ: + INSTALL_PATH = os.environ["INSTALL_PATH"] +else: + INSTALL_PATH = os.path.abspath(os.path.join(ORFS_FLOW_DIR, "../tools/install")) +LOCAL_DIR = "" + # Global variable for args args = None @@ -448,7 +457,7 @@ def parse_arguments(): # If the experiment name is the default, add a UUID to the end. if args.experiment == "test": - id = str(uuid())[:8] + id = time.strftime("%Y%m%d-%H%M%S") + "-" + str(uuid())[:4] args.experiment = f"{args.mode}-{id}" else: args.experiment += f"-{args.mode}" @@ -558,31 +567,38 @@ def save_best(results): def sweep(): """Run sweep of parameters""" - if args.server is not None: - # For remote sweep we create the following directory structure: - # 1/ 2/ 3/ 4/ - # //// - repo_dir = os.path.abspath(LOCAL_DIR + "/../" * 4) - else: - repo_dir = os.path.abspath(os.path.join(ORFS_FLOW_DIR, "..")) + repo_dir = os.path.abspath(os.path.join(ORFS_FLOW_DIR, "..")) print(f"[INFO TUN-0012] Log folder {LOCAL_DIR}.") - queue = Queue() parameter_list = list() + total_combinations = 1 for name, content in config_dict.items(): + print(f"[INFO TUN-0013] Sweeping {name} over {content}.") if not isinstance(content, list): print(f"[ERROR TUN-0015] {name} sweep is not supported.") sys.exit(1) if content[-1] == 0: print("[ERROR TUN-0014] Sweep does not support step value zero.") sys.exit(1) - parameter_list.append([{name: i} for i in np.arange(*content)]) + parameter_list.append([{name: i} for i in np.round(np.arange(*content), 4).tolist()]) + total_combinations *= len(parameter_list[-1]) + print(f"[INFO TUN-0017] Total of {total_combinations} combinations.") + if total_combinations > 1000: + print( + "[WARN TUN-0033] The total number of combinations is very large." + " Do you wish to continue? (y/n)" + ) + if input().lower() != "y": + sys.exit(1) parameter_list = list(product(*parameter_list)) + queue = Queue() for parameter in parameter_list: - temp = dict() + params_to_sweep = dict() for value in parameter: - temp.update(value) - queue.put([args, repo_dir, temp, SDC_ORIGINAL, FR_ORIGINAL, INSTALL_PATH]) - workers = [consumer.remote(queue) for _ in range(args.jobs)] + params_to_sweep.update(value) + queue.put([args, repo_dir, params_to_sweep, SDC_ORIGINAL, FR_ORIGINAL, INSTALL_PATH]) + max_jobs = min(args.jobs, queue.qsize()) + print(f"[INFO TUN-0008] Starting sweep with {max_jobs} workers.") + workers = [consumer.remote(queue) for _ in range(max_jobs)] print("[INFO TUN-0009] Waiting for results.") ray.get(workers) print("[INFO TUN-0010] Sweep complete.") @@ -592,14 +608,13 @@ def main(): global args, SDC_ORIGINAL, FR_ORIGINAL, LOCAL_DIR, INSTALL_PATH, ORFS_FLOW_DIR, config_dict, reference, best_params args = parse_arguments() + LOCAL_DIR = os.path.join(ORFS_FLOW_DIR, f"logs/{args.platform}/{args.design}") # Read config and original files before handling where to run in case we # need to upload the files. config_dict, SDC_ORIGINAL, FR_ORIGINAL = read_config( os.path.abspath(args.config), args.mode, getattr(args, "algorithm", None) ) - LOCAL_DIR, ORFS_FLOW_DIR, INSTALL_PATH = prepare_ray_server(args) - if args.mode == "tune": best_params = set_best_params(args.platform, args.design) search_algo = set_algorithm( diff --git a/tools/AutoTuner/src/autotuner/utils.py b/tools/AutoTuner/src/autotuner/utils.py index fadab40325..448d771c91 100644 --- a/tools/AutoTuner/src/autotuner/utils.py +++ b/tools/AutoTuner/src/autotuner/utils.py @@ -40,9 +40,8 @@ import yaml import subprocess import sys -import uuid +from uuid import uuid4 as uuid import time -from multiprocessing import cpu_count from datetime import datetime import numpy as np @@ -319,9 +318,8 @@ def openroad( if install_path is None: install_path = os.path.join(base_dir, "tools/install") - export_command = f"export PATH={install_path}/OpenROAD/bin" - export_command += f":{install_path}/yosys/bin:$PATH" - export_command += " && " + export_command = f"export OPENROAD_EXE={install_path}/OpenROAD/bin/openroad &&" + export_command += f" export YOSYS_EXE={install_path}/yosys/bin/yosys &&" make_command = export_command if args.memory_limit is not None: @@ -610,28 +608,6 @@ def read_tune_pbt(name, this): return config, sdc_file, fr_file -def prepare_ray_server(args): - """ - Prepares Ray server and returns basic directories. - """ - # Connect to remote Ray server if any, otherwise will run locally - if args.server is not None: - # Connect to ray server before first remote execution. - ray.init(f"ray://{args.server}:{args.port}") - print("[INFO TUN-0001] Connected to Ray server.") - # Common variables used for local and remote runs. - orfs_dir = getattr(args, "orfs", None) - orfs_flow_dir = os.path.abspath( - os.path.join(orfs_dir, "flow") - if orfs_dir - else os.path.join(os.path.dirname(__file__), "../../../../flow") - ) - local_dir = f"logs/{args.platform}/{args.design}" - local_dir = os.path.join(orfs_flow_dir, local_dir) - install_path = os.path.abspath(os.path.join(orfs_flow_dir, "../tools/install")) - return local_dir, orfs_flow_dir, install_path - - @ray.remote def openroad_distributed( args, @@ -643,6 +619,8 @@ def openroad_distributed( variant=None, ): """Simple wrapper to run openroad distributed with Ray.""" + if variant is None and len(config) != 1: + variant = "multiple-params" config = parse_config( config=config, base_dir=repo_dir, @@ -655,11 +633,12 @@ def openroad_distributed( if variant is None: variant = config.replace(" ", "_").replace("=", "_") t = time.time() + id = time.strftime("%Y%m%d-%H%M%S") + "-" + str(uuid())[:4] metric_file = openroad( args=args, base_dir=repo_dir, parameters=config, - flow_variant=f"{uuid.uuid4()}-{variant}" if variant else f"{uuid.uuid4()}", + flow_variant=f"{id}-{variant}" if variant else id, install_path=install_path, ) duration = time.time() - t @@ -671,7 +650,7 @@ def consumer(queue): """consumer""" while not queue.empty(): next_item = queue.get() - name = next_item[1] + name = next_item[2] print(f"[INFO TUN-0007] Scheduling run for parameter {name}.") ray.get(openroad_distributed.remote(*next_item)) print(f"[INFO TUN-0008] Finished run for parameter {name}.")