diff --git a/model_zoo/datasets.py b/model_zoo/datasets.py index d5fd84e84cdc88c1627273c4e11f8a502a50d81c..ac519bfe26ba48429cf40d6f16fdc3587e606b37 100644 --- a/model_zoo/datasets.py +++ b/model_zoo/datasets.py @@ -7,7 +7,7 @@ import torch from torch.utils.data.dataset import Dataset RetT = Tuple[torch.Tensor, torch.Tensor] -msg_logger = logging.getLogger() +msg_logger = logging.getLogger(__name__) PathLike = Union[Path, str] diff --git a/predtuner/approxapp.py b/predtuner/approxapp.py index 5b38a8ded97ff00e108893ca3ec4b63854dcc7ce..9f0178ff5599baf13e8496b9200d4d5a73a5b57c 100644 --- a/predtuner/approxapp.py +++ b/predtuner/approxapp.py @@ -1,7 +1,7 @@ import abc import logging from pathlib import Path -from typing import Dict, Generic, List, Optional, Tuple, Type, TypeVar +from typing import Callable, Dict, Generic, List, Optional, Tuple, Type, TypeVar import matplotlib.pyplot as plt import numpy as np @@ -111,6 +111,14 @@ class ApproxTuner(Generic[T]): from ._dbloader import read_opentuner_db + n_ops, n_knobs = len(self.app.ops), len(self.app.knobs) + msg_logger.info( + "Started tuning app %s with %d ops and %d unique knob types", + self.app.name, + n_ops, + n_knobs, + ) + msg_logger.info("At most %d iterations", max_iter) opentuner_args = opentuner_default_args() tuner = self._get_tuner_interface( opentuner_args, @@ -124,6 +132,11 @@ class ApproxTuner(Generic[T]): trm = TuningRunMain(tuner, opentuner_args) # TuningRunMain.__init__ initializes its own logger, so we'll override it and use ours override_opentuner_config() + msg_logger.info( + "Estimated size of search space: %d", trm.manipulator.search_space_size() + ) + # A little bit of hack to get the _real_ progress when duplicated configs exist + tuner.set_progress_getter(lambda: trm.search_driver.test_count) # This is where opentuner runs trm.main() # Parse and store results @@ -137,7 +150,16 @@ class ApproxTuner(Generic[T]): cfg for cfg in self.all_configs if cfg.qos > self.keep_threshold ] self.best_configs = self.take_best_configs(self.kept_configs, take_best_n) + msg_logger.info( + "Tuning finished with %d configs in total, " + "%d configs above keeping threshold, " + "and %d configs selected on tradeoff curve", + len(self.all_configs), + len(self.kept_configs), + len(self.best_configs), + ) if calibrate: + msg_logger.info("Calibrating configurations on calibration inputs") self.calibrate_configs_(self.best_configs) return self.best_configs @@ -206,6 +228,11 @@ class ApproxTuner(Generic[T]): qos_tuner_threshold = baseline_qos - qos_tuner_threshold self.keep_threshold = baseline_qos - self.keep_threshold opentuner_args.test_limit = max_iter + msg_logger.info( + "Tuner QoS threshold: %f; keeping configurations with QoS >= %f", + qos_tuner_threshold, + self.keep_threshold, + ) return TunerInterface( opentuner_args, self.app, @@ -226,7 +253,9 @@ class ApproxTuner(Generic[T]): def opentuner_default_args(): from opentuner import default_argparser - return default_argparser().parse_args([]) + args = default_argparser().parse_args([]) + args.no_dups = True # Don't print duplicated config warnings + return args class TunerInterface(MeasurementInterface): @@ -259,6 +288,9 @@ class TunerInterface(MeasurementInterface): objective=objective, ) + def set_progress_getter(self, getter: Callable[[], int]): + self.progress_getter = getter + def manipulator(self) -> ConfigurationManipulator: """Define the search space by creating a ConfigurationManipulator.""" manipulator = ConfigurationManipulator() @@ -275,9 +307,12 @@ class TunerInterface(MeasurementInterface): qos, perf = self.app.measure_qos_perf(cfg, False, **self.app_kwargs) # Print a debug message for each config in tuning and keep threshold self.print_debug_config(qos, perf) - self.pbar.update() + self.pbar.update(self.progress_getter() - self.pbar.n) return Result(time=perf, accuracy=qos) + def save_final_config(self, config): + self.pbar.close() + def print_debug_config(self, qos: float, perf: float): gt_tune, gt_keep = qos > self.tune_thres, qos > self.keep_thres if not gt_tune and not gt_keep: @@ -289,5 +324,5 @@ class TunerInterface(MeasurementInterface): else: kind = "tuning and keep" msg_logger.debug( - f"Found config in {kind} threshold: QoS = {qos}, perf = {perf}" + f"Found config within {kind} threshold: QoS = {qos}, perf = {perf}" ) diff --git a/predtuner/modeledapp.py b/predtuner/modeledapp.py index fd224fd87943f562ccd3f4e50b0b5dac1c91ca80..6bd15bb23cd5775bc462a9991239134c0a58d76e 100644 --- a/predtuner/modeledapp.py +++ b/predtuner/modeledapp.py @@ -224,9 +224,8 @@ class QoSModelP1(IQoSModel): self._try_append_save(self.storage, op, knob, delta_tensor) super()._init() - @staticmethod - def _load(path: Path) -> Iterator[Tuple[str, str, torch.Tensor]]: - msg_logger.info(f"Found pickle at {path}") + def _load(self, path: Path) -> Iterator[Tuple[str, str, torch.Tensor]]: + msg_logger.info(f"Model {self.name} found saved model at {path}") with path.open("rb") as f: while True: try: @@ -314,6 +313,7 @@ class QoSModelP2(IQoSModel): ) else: msg_logger.warning("Loaded profile does not have app name identifier") + msg_logger.info(f"Model {self.name} loaded saved model at {path}") return df, baseline_qos def _save(self, path: Path): @@ -360,9 +360,20 @@ class ApproxModeledTuner(ApproxTuner): perf_model: str = "none", qos_model: str = "none", ) -> List[ValConfig]: + qos_desc = ( + "no model for qos" if qos_model == "none" else f'qos model "{qos_model}"' + ) + perf_desc = ( + "no model for performance" + if perf_model == "none" + else f'performance model "{perf_model}"' + ) + msg_logger.info("Starting tuning with %s and %s", qos_desc, perf_desc) if qos_model != "none": + msg_logger.info("Initializing qos model %s", qos_model) self.app._init_model(qos_model) if perf_model != "none": + msg_logger.info("Initializing performance model %s", perf_model) self.app._init_model(perf_model) ret = super().tune( max_iter=max_iter, @@ -374,9 +385,13 @@ class ApproxModeledTuner(ApproxTuner): perf_model=perf_model, qos_model=qos_model, ) - if validate is None: - validate = qos_model != "none" - if validate: + if validate is None and qos_model != "none": + msg_logger.info( + 'Validating configurations due to using qos model "%s"', qos_model + ) + self.validate_configs_(self.best_configs) + elif validate: + msg_logger.info("Validating configurations as user requested") self.validate_configs_(self.best_configs) return ret diff --git a/predtuner/torchutil/summary.py b/predtuner/torchutil/summary.py index b6e150db92f09aa7cd93ed363fa9ca325ee49f2e..beb4cb1ba860a8339df83c3b7153dc4fc38f2d74 100644 --- a/predtuner/torchutil/summary.py +++ b/predtuner/torchutil/summary.py @@ -1,12 +1,15 @@ +import logging from collections import OrderedDict from typing import Iterable, Tuple import pandas import torch import torch.nn as nn + from .indexing import ModuleIndexer _summary_used = False +msg_logger = logging.getLogger(__name__) def get_flops(module: nn.Module, input_shape, output_shape): @@ -50,12 +53,12 @@ def get_flops(module: nn.Module, input_shape, output_shape): handler = type_dispatch.get(type(module)) if not handler: if not list(module.children()): - _print_once(f"Leaf module {module} cannot be handled") + _warn_once(f"Module {module} cannot be handled; its FLOPs will be estimated as 0") return 0.0 try: return handler() except RuntimeError as e: - _print_once(f'Error "{e}" when handling {module}') + _warn_once(f'Error "{e}" when handling {module}; its FLOPs will be estimated as 0') return 0.0 @@ -92,7 +95,7 @@ def get_summary(model: nn.Module, model_args: Tuple) -> pandas.DataFrame: params=n_params, flops=flops, trainable=trainable, - is_leaf=is_leaf + is_leaf=is_leaf, ) def register_hook(module: nn.Module): @@ -127,9 +130,9 @@ def default_handle_sizes(value): if isinstance(value, Iterable): return [list(i.size()) for i in value] except AttributeError as e: - _print_once(f"Cannot handle {type(value)}: error {e}") + _warn_once(f"Cannot get shape of {type(value)}: error {e}") return None - _print_once(f"Cannot handle {type(value)}") + _warn_once(f"Cannot get shape of {type(value)}") return None @@ -143,7 +146,7 @@ def _get_numel(shape): return torch.prod(torch.tensor(shape)).item() -def _print_once(*args, **kwargs): +def _warn_once(*args, **kwargs): if _summary_used: return - print(*args, **kwargs) + msg_logger.warning(*args, **kwargs)