diff --git a/predtuner/approxapp.py b/predtuner/approxapp.py index a6513cf19dea61fa17d6e541d8fa56b568b1c336..b420d8deaf35ad3ac25f8d3a6ef507997c9d3116 100644 --- a/predtuner/approxapp.py +++ b/predtuner/approxapp.py @@ -113,9 +113,13 @@ class Config: self.knobs = dict(sorted(knobs.items())) self.test_qos: Optional[float] = test_qos + @property + def speedup(self): + return 1 / self.cost + @property def qos_speedup(self): - return self.qos, 1 / self.cost + return self.qos, self.speedup T = TypeVar("T", bound=Config) diff --git a/predtuner/pipedbin.py b/predtuner/pipedbin.py index 9b9723a75d1d35fb77e407a22e9b540592642500..b5d525ddf8e81f53271afde486f43a597b26a1a5 100644 --- a/predtuner/pipedbin.py +++ b/predtuner/pipedbin.py @@ -1,9 +1,9 @@ import json import os from pathlib import Path -from typing import Dict, List, Optional, Tuple, Union -import numpy as np +from typing import Dict, List, Optional, Sequence, Tuple, Union +import numpy as np import torch from .approxapp import ApproxKnob, BaselineKnob, KnobsT @@ -14,6 +14,7 @@ from .modeledapp import ( ModeledApp, QoSModelP1, QoSModelP2, + ValConfig, ) from .torchutil import accuracy @@ -58,6 +59,30 @@ class PipedBinaryApp(ModeledApp): self.process = None self._invoke_binary() + # Measure our own test set accuracy for config export purpose + self.base_test_qos, _ = self.empirical_measure_qos_cost({}, True) + + def dump_hpvm_configs( + self, + configs: Sequence[ValConfig], + output: PathLike = None, + add_baseline_conf: bool = True, + ): + configs = list(configs) + tqos = self.base_test_qos + if add_baseline_conf: + base_knobs = self.add_baseline_to_knobs({}) + base_conf = ValConfig(tqos, 1.0, base_knobs, tqos, tqos) + configs = [base_conf] + configs + configs = sorted(configs, key=lambda c: c.speedup) + conf_str = self.knob_exporter.configs_to_str(configs, tqos) + if output is None: + return conf_str + output_p = Path(output) + os.makedirs(output_p.parent, exist_ok=True) + with output_p.open("w") as f: + f.write(conf_str) + @property def name(self) -> str: """Name of application. Acts as an identifier in many places, so @@ -107,7 +132,7 @@ class PipedBinaryApp(ModeledApp): self._check_running() conf = self.add_baseline_to_knobs(with_approxes) with self.conf_file.open("w") as f: - f.write(self.knob_exporter.to_str(conf)) + f.write(self.knob_exporter.knobs_to_str(conf)) with self.fifo_w_file.open("w") as f: f.write("test" if is_test else "tune") ret = read_till_end(self.fifo_r_file) @@ -243,26 +268,41 @@ class HPVMConfigBuilder: self.types = self._parse_ops(ops) self.merged_to_original = self._find_merge_chains(self.types) - def to_str(self, config: KnobsT) -> str: - def print_op(op_index: int): - ty = self.types[op_index] - knob_value = config[self.ops[op_index]] - out_knob_ty = self.knob_to_knob[knob_value] - out_op_ty = self.op_to_op.get(ty, ty) - return f"{out_op_ty} {out_knob_ty} {knob_value}" + def knobs_to_str(self, knobs: KnobsT) -> str: + dummy_conf = ValConfig(1.0, 1.0, knobs, 1.0, 1.0) + return self.configs_to_str([dummy_conf], 1.0) + + def configs_to_str(self, configs: Sequence[ValConfig], base_test_qos: float) -> str: + body = "\n".join( + self.config_to_str(config, base_test_qos, i) + for i, config in enumerate(configs) + ) + return f"0.0\n{body}" + def config_to_str( + self, config: ValConfig, base_test_qos: float, conf_idx: int = 1 + ) -> str: def print_line(line_index: int, op_indices): - return f"{line_index} gpu " + " ".join(print_op(idx) for idx in op_indices) + ops = " ".join(self._print_op(config.knobs, idx) for idx in op_indices) + return f"{line_index} gpu {ops}" - if len(config) != len(self.ops): + if len(config.knobs) != len(self.ops): raise ValueError(f"Incorrect config length, expected {len(self.ops)}") - prefix = ["0.0", "+++++", "conf1 0.0 0.0 0.0 0.0"] - suffix = ["-----"] + test_qos = config.test_qos + qos_loss = base_test_qos - config.test_qos + conf_header = f"conf{conf_idx} {config.speedup} 1.0 {test_qos} {qos_loss}" body_lines = [ print_line(line_idx, orig_indices) for line_idx, orig_indices in enumerate(self.merged_to_original, start=1) ] - return "\n".join(prefix + body_lines + suffix) + return "\n".join(["+++++", conf_header] + body_lines + ["-----"]) + + def _print_op(self, knobs: KnobsT, op_index: int): + ty = self.types[op_index] + knob_value = knobs[self.ops[op_index]] + out_knob_ty = self.knob_to_knob[knob_value] + out_op_ty = self.op_to_op.get(ty, ty) + return f"{out_op_ty} {out_knob_ty} {knob_value}" @staticmethod def _parse_ops(ops: List[str]) -> List[str]: