From 5873347b901ec9b1b998b2651d7706cd86205363 Mon Sep 17 00:00:00 2001
From: Yifan Zhao <yifanz16@illinois.edu>
Date: Mon, 25 Jan 2021 05:40:43 -0600
Subject: [PATCH] Supports passing param for model saving path from torchapp

---
 predtuner/modeledapp.py | 27 ++++++++++++++++++---------
 predtuner/torchapp.py   | 22 +++++++++++-----------
 2 files changed, 29 insertions(+), 20 deletions(-)

diff --git a/predtuner/modeledapp.py b/predtuner/modeledapp.py
index b4eee06..fd224fd 100644
--- a/predtuner/modeledapp.py
+++ b/predtuner/modeledapp.py
@@ -163,9 +163,9 @@ class LinearPerfModel(IPerfModel):
 
     def measure_perf(self, with_approxes: KnobsT) -> float:
         """We implement this using a weighted linear performance model."""
-        return float(sum(
-            self.cost_df.loc[layer, knob] for layer, knob in with_approxes.items()
-        ))
+        return float(
+            sum(self.cost_df.loc[layer, knob] for layer, knob in with_approxes.items())
+        )
 
 
 class QoSModelP1(IQoSModel):
@@ -212,7 +212,7 @@ class QoSModelP1(IQoSModel):
 
     def _init(self):
         dt = self.delta_tensors
-        btensor = self.baseline_tensor 
+        btensor = self.baseline_tensor
         if self.storage and self.storage.is_file():
             for op, knob, delta_tensor in self._load(self.storage):
                 dt[op][knob] = delta_tensor
@@ -239,9 +239,12 @@ class QoSModelP1(IQoSModel):
     def _try_append_save(
         path: Optional[Path], op_name: str, knob_name: str, tensor: torch.Tensor
     ):
+        import os
+
         if not path:
             return
-        path.touch(exist_ok=True)
+        if not path.parent.is_dir():
+            os.makedirs(path.parent)
         with path.open("ab") as f:
             pickle.dump((op_name, knob_name, tensor), f)
 
@@ -272,9 +275,10 @@ class QoSModelP2(IQoSModel):
 
     def measure_qos(self, with_approxes: KnobsT) -> float:
         assert self.baseline_qos is not None and self.qos_df is not None
-        delta_qoses = np.array(
-            [self.qos_df.loc[kv] for kv in with_approxes.items()]
-        ) - self.baseline_qos
+        delta_qoses = (
+            np.array([self.qos_df.loc[kv] for kv in with_approxes.items()])
+            - self.baseline_qos
+        )
         ret = delta_qoses.sum() + self.baseline_qos
         assert not np.isnan(ret)
         return float(ret)
@@ -285,10 +289,11 @@ class QoSModelP2(IQoSModel):
         else:
             knob_names = [k.name for k in self.app.knobs]
             self.qos_df = pd.DataFrame(index=self.app.ops, columns=knob_names)
+            self.qos_df = self.qos_df.where(pd.notnull(self.qos_df), None)
             self.baseline_qos = self._empirical_measure_qos({})
         df = self.qos_df
         for op, knob in barred_ravel_knobs(self.app):
-            if not np.isnan(df.loc[op, knob]):
+            if df.loc[op, knob] is not None:
                 continue
             df.loc[op, knob] = self._empirical_measure_qos({op: knob})
         if self.storage and not self.storage.is_file():
@@ -312,6 +317,10 @@ class QoSModelP2(IQoSModel):
         return df, baseline_qos
 
     def _save(self, path: Path):
+        import os
+
+        if not path.parent.is_dir():
+            os.makedirs(path.parent)
         with path.open("w") as f:
             json.dump(
                 {
diff --git a/predtuner/torchapp.py b/predtuner/torchapp.py
index ae62d3b..2e781ac 100644
--- a/predtuner/torchapp.py
+++ b/predtuner/torchapp.py
@@ -1,20 +1,16 @@
 import abc
-from typing import Any, Callable, Dict, List, Set, Tuple, Union
+from pathlib import Path
+from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
 
 import numpy as np
 import torch
 from torch.nn import Module
 from torch.utils.data.dataloader import DataLoader
 
+from ._logging import PathLike
 from .approxapp import ApproxKnob, KnobsT
-from .modeledapp import (
-    IPerfModel,
-    IQoSModel,
-    LinearPerfModel,
-    ModeledApp,
-    QoSModelP1,
-    QoSModelP2,
-)
+from .modeledapp import (IPerfModel, IQoSModel, LinearPerfModel, ModeledApp,
+                         QoSModelP1, QoSModelP2)
 from .torchutil import ModuleIndexer, get_summary, move_to_device_recursively
 
 
@@ -64,6 +60,7 @@ class TorchApp(ModeledApp, abc.ABC):
         tensor_to_qos: Callable[[torch.Tensor, Any], float],
         combine_qos: Callable[[np.ndarray], float] = np.mean,
         device: Union[torch.device, str] = _default_device,
+        model_storage_folder: Optional[PathLike] = None
     ) -> None:
         self.app_name = app_name
         self.module = module
@@ -73,6 +70,7 @@ class TorchApp(ModeledApp, abc.ABC):
         self.tensor_to_qos = tensor_to_qos
         self.combine_qos = combine_qos
         self.device = device
+        self.model_storage = Path(model_storage_folder)
 
         self.module = self.module.to(device)
         self.midx = ModuleIndexer(module)
@@ -113,10 +111,12 @@ class TorchApp(ModeledApp, abc.ABC):
                 qoses.append(qos)
             return self.combine_qos(np.array(qoses))
 
+        p1_storage = self.model_storage / "p1.pkl" if self.model_storage else None
+        p2_storage = self.model_storage / "p2.json" if self.model_storage else None
         return [
             LinearPerfModel(self._op_costs, self._knob_speedups),
-            QoSModelP1(self, self._get_raw_output_valset, batched_valset_qos),
-            QoSModelP2(self),
+            QoSModelP1(self, self._get_raw_output_valset, batched_valset_qos, p1_storage),
+            QoSModelP2(self, p2_storage),
         ]
 
     @torch.no_grad()
-- 
GitLab