diff --git a/hpvm/docs/components/hpvm-profiler.rst b/hpvm/docs/components/hpvm-profiler.rst index 8a0e6603d3b7111d2735a86b5db26d7aa834ebb6..820456799ddf0570be6b92564e35077e31fcd3da 100644 --- a/hpvm/docs/components/hpvm-profiler.rst +++ b/hpvm/docs/components/hpvm-profiler.rst @@ -1,6 +1,6 @@ HPVM Profiler API ====================== -.. autofunction:: hpvm_profiler.profile_configs +.. autofunction:: hpvm_profiler.profile_config_file .. autofunction:: hpvm_profiler.plot_hpvm_configs diff --git a/hpvm/docs/getting-started.rst b/hpvm/docs/getting-started.rst index cbf5d0b095c1035acf8bfcc7aaccf1d3cb0be3ce..7b6b3074275c7e693270fe658223e5183b76816d 100644 --- a/hpvm/docs/getting-started.rst +++ b/hpvm/docs/getting-started.rst @@ -207,14 +207,14 @@ we obtained in the tuning step. .. code-block:: python - from hpvm_profiler import profile_configs, plot_hpvm_configs + from hpvm_profiler import profile_config_file, plot_hpvm_configs # Set `target_binary` to the path of the plain binary. target_binary = "./alexnet2_cifar10/build/alexnet2_cifar10" # Set `config_file` to the config file produced in tuning, such as "hpvm_confs.txt". config_file = "hpvm_confs.txt" out_config_file = "hpvm_confs_profiled.txt" - profile_configs(target_binary, config_file, out_config_file) + profile_config_file(target_binary, config_file, out_config_file) plot_hpvm_configs(out_config_file, "configs_profiled.png") ``hpvm_confs_profiled.txt`` contains the profiled configurations in HPVM format, diff --git a/hpvm/projects/hpvm-profiler/hpvm_profiler/__init__.py b/hpvm/projects/hpvm-profiler/hpvm_profiler/__init__.py index 4e91fbbe4a4af2c16b7583443360a09d88b0ac61..594a96b202e0526e3c3dae5263b195fc8db95a94 100644 --- a/hpvm/projects/hpvm-profiler/hpvm_profiler/__init__.py +++ b/hpvm/projects/hpvm-profiler/hpvm_profiler/__init__.py @@ -4,16 +4,16 @@ from subprocess import PIPE, CalledProcessError from typing import Iterable, List, Tuple, Union import matplotlib.pyplot as plt -from tqdm import trange PathLike = Union[Path, str] conf_opening, conf_closing = "+++++", "-----" -def profile_configs( +def profile_config_file( binary_path: PathLike, config_path: PathLike, output_config_path: PathLike, + progress_bar: bool = True, profile_filename: str = "profile_info.txt", qos_filename: str = "final_accuracy", ) -> None: @@ -33,39 +33,64 @@ def profile_configs( It contains a single float number as the QoS of this run. This defaults to "final_accuracy" and should not be changed for HPVM binaries. """ - - from subprocess import check_call - from tempfile import NamedTemporaryFile - # Read first line ("the float") and configs in config file header, configs = read_hpvm_configs(Path(config_path)) if not configs: raise ValueError("Config file with no configs is unsupported.") - temp_file = NamedTemporaryFile("w") - baseline_time, baseline_acc = None, None - for idx in trange(len(configs), desc="Configs profiled"): - config = configs[idx] - # Write config to temp config file - write_hpvm_config(header, [config], Path(temp_file.name)) - # Run binary_path binary, - # which generates `profile_filename` and `qos_filename` file in cwd. - try: - check_call([str(binary_path), "-c", str(temp_file.name)]) - except CalledProcessError as e: - print("Output from the program:") - print(e.output) - raise e - # Read these two files for time and QoS info. - time = _read_profile_file(Path(profile_filename)) - acc = _read_qos_file(Path(qos_filename)) - if idx == 0: - baseline_time, baseline_acc = time, acc - continue - assert baseline_time is not None and baseline_acc is not None + # Modifies configs in place. + profile_configs( + binary_path, configs[1:], configs[0], progress_bar, profile_filename, qos_filename + ) + write_hpvm_configs(header, configs, Path(output_config_path)) + + +def profile_configs( + binary_path: PathLike, + configs: Iterable["Config"], + baseline_config: "Config", + progress_bar: bool = True, + profile_filename: str = "profile_info.txt", + qos_filename: str = "final_accuracy", +) -> None: + """Profile a sequence of HPVM configs. + This function modifies argument `configs` in place.""" + + from tqdm import tqdm + + baseline_time, baseline_acc = profile_config(binary_path, baseline_config) + iterable = tqdm(configs, desc="Configs profiled") if progress_bar else configs + for idx in iterable: + time, acc = profile_config(binary_path, config, profile_filename, qos_filename) speedup = baseline_time / time config.update_profile_results(speedup, acc, baseline_acc) - write_hpvm_config(header, configs, Path(output_config_path)) + return configs + + +def measure_config( + binary_path: PathLike, + config: "Config", + profile_filename: str = "profile_info.txt", + qos_filename: str = "final_accuracy", +): + from subprocess import check_call + from tempfile import NamedTemporaryFile + import os + + temp_file = NamedTemporaryFile("w") + write_hpvm_configs("0.0", [config], Path(temp_file.name)) + # Run binary_path binary, + # which generates `profile_filename` and `qos_filename` file in cwd. + try: + with open(os.devnull, "w") as f: + check_call([str(binary_path), "-c", str(temp_file.name)], stdout=f) + except CalledProcessError as e: + print("Output from the program:") + print(e.output) + raise e + time = _read_profile_file(Path(profile_filename)) + acc = _read_qos_file(Path(qos_filename)) temp_file.close() + return time, acc def plot_hpvm_configs( @@ -157,8 +182,7 @@ def read_hpvm_configs(config_file: PathLike) -> Tuple[str, List[Config]]: return header, ret_configs -def write_hpvm_config(header: str, configs: Iterable[Config], to_file: PathLike): - +def write_hpvm_configs(header: str, configs: Iterable[Config], to_file: PathLike): text_segs = [header] + [str(config) for config in configs] with open(to_file, "w") as f: f.write("\n".join(text_segs))