Source code for hpvm_profiler

from pathlib import Path
from subprocess import PIPE, CalledProcessError
from typing import Iterable, List, Tuple, Union

import matplotlib.pyplot as plt

PathLike = Union[Path, str]
conf_opening, conf_closing = "+++++", "-----"


[docs]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: r"""Profile an HPVM configuration file with an HPVM binary, and write the updated configuration file to a given location. The configuration file must have the baseline as the first configuration. :param binary_path: Path to binary to be executed in profiling. :param config_path: Path to config file (HPVM configuration format) with configs to enumerate for profiling. :param output_config_path: Path where the output configs are written. The output config file has the same configs as the input `config_path` file, but the performance and energy readings are updated. :param progress_bar: If `True`, show a progress bar for number of configs already profiled. :param profile_filename: Name of profile file generated by the binary (in current directory). This defaults to "profile_info.txt" and should not be changed for HPVM binaries. :param qos_filename: Name of QoS file generated by the binary (in current directory). 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. """ # 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.") # 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 = measure_config(binary_path, baseline_config) iterable = tqdm(configs, desc="Configs profiled") if progress_bar else configs for config in iterable: time, acc = measure_config(binary_path, config, profile_filename, qos_filename) speedup = baseline_time / time config.update_profile_results(speedup, acc, baseline_acc) 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
[docs]def plot_hpvm_configs( config_path: PathLike, save_to: PathLike = None, show_qos_loss: bool = True, **fig_kwargs, ) -> plt.Figure: """ Plot the QoS-speedup information in an HPVM configuration file. It is recommended to profile the config file first (using `profile_configs`) to obtain real speedup numbers. This function creates a `matplotlib.pyplot.Figure`, plots on it, and returns it. :param config_path: Path to the config file (HPVM configuration format). :param save_to: File to save figure into. Default is None: don't save figure (just return it). :param show_qos_loss: Show the loss of QoS on x axis of the figure. Defaults to True. If False, will use (absolute) QoS instead of QoS loss. :param fig_kwargs: Arguments to pass to `plt.subplots`. """ import numpy as np _, configs = read_hpvm_configs(config_path) get_qos = lambda c: c.qos_loss if show_qos_loss else c.qos qos_speedup = np.array([(get_qos(c), c.speedup) for c in configs]) qoses, speedups = qos_speedup.T fig, ax = plt.subplots(**fig_kwargs) ax.scatter(qoses, speedups) ax.set_xlabel("QoS Loss") ax.set_ylabel("Speedup (X)") if save_to: fig.savefig(save_to, dpi=300) return fig
class Config: def __init__( self, conf_name: str, speedup: float, energy: float, qos: float, qos_loss: float, config_body: List[str], ): self.conf_name = conf_name self.speedup = speedup self.energy = energy self.qos = qos self.qos_loss = qos_loss # We don't care about the information in this part, and we don't parse this. self.config_body = config_body def update_profile_results(self, speedup: float, qos: float, base_qos: float): recorded_base_qos = self.qos + self.qos_loss if abs(recorded_base_qos - base_qos) > 0.025: raise ValueError( f"Baseline QoS mismatch. Original: {recorded_base_qos}, measured: {base_qos}" ) self.speedup = speedup self.qos = qos self.qos_loss = base_qos - qos def __repr__(self) -> str: header_fields = [ self.conf_name, self.speedup, self.energy, self.qos, self.qos_loss, ] header = " ".join(str(field) for field in header_fields) lines = [conf_opening, header, *self.config_body, conf_closing] return "\n".join(lines) __str__ = __repr__ def read_hpvm_configs(config_file: PathLike) -> Tuple[str, List[Config]]: # def read_hpvm_configs(config_file, config_num, temp_file): ret_configs = [] with open(config_file) as f: text = f.read() # There's 1 float sitting on the first line of config file. # We don't use it, but want to keep that intact. header, *configs = text.split(conf_opening) header = header.strip() for config_text in configs: config_text = config_text.replace(conf_closing, "").strip() config_header, *config_body = config_text.splitlines() conf_name, *number_fields = config_header.split(" ") speedup, energy, qos, qos_drop = [float(s) for s in number_fields] ret_configs.append( Config(conf_name, speedup, energy, qos, qos_drop, config_body) ) return header, ret_configs 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)) f.flush() def _read_profile_file(profile_file_path: Path): with profile_file_path.open() as f: target_lines = [line.strip() for line in f if "Total Time" in line] if len(target_lines) != 1: raise RuntimeError(f"Profile {profile_file_path} malformed") (target_line,) = target_lines return float(target_line.split()[3]) def _read_qos_file(qos_file_path: Path): with qos_file_path.open() as f: return float(f.read().strip())