From 8d344c4e5ebbe56da8fefb26061b56cbefd26b95 Mon Sep 17 00:00:00 2001
From: Yifan Zhao <yifanz16@illinois.edu>
Date: Mon, 15 Mar 2021 14:35:30 -0500
Subject: [PATCH] Added documentation, improved sample and added small fixes

---
 doc/getting_started.rst        | 131 +++++++++++++++++++++++++++++++++
 doc/index.rst                  |  33 +++------
 doc/tuning_result.png          | Bin 0 -> 25922 bytes
 examples/tune_vgg16_cifar10.py |  14 ++--
 predtuner/approxapp.py         |   5 +-
 5 files changed, 153 insertions(+), 30 deletions(-)
 create mode 100644 doc/tuning_result.png

diff --git a/doc/getting_started.rst b/doc/getting_started.rst
index 75381aa..867707b 100644
--- a/doc/getting_started.rst
+++ b/doc/getting_started.rst
@@ -1,2 +1,133 @@
 Getting Started
 ===================
+
+This guide can help you start working with PredTuner.
+
+Installation
+------------
+
+Install PredTuner from source using `pip`:
+
+.. code-block:: shell
+
+   pip install -e .
+
+PredTuner will also be available on PyPi in the future after we publish the first release.
+
+Tuning a PyTorch DNN
+--------------------
+
+PredTuner can tune any user-defined application,
+but it is optimized for tuning DNN applications defined in PyTorch.
+
+We will use models predefined in PredTuner for demonstration purposes.
+Download pretrained VGG16 model parameters and CIFAR10 dataset from `here
+<https://drive.google.com/file/d/1Z84z-nsv_nbrr8t9i28UoxSJg-Sd_Ddu/view?usp=sharing>`_.
+After extraction, there should be a :code:`model_params/` folder in current directory.
+
+Load the tuning and test subsets of CIFAR10 dataset, and create a pretrained VGG16 model:
+
+.. code-block:: python
+  
+  from pathlib import Path
+  import predtuner as pt
+  from predtuner.model_zoo import CIFAR, VGG16Cifar10
+
+  prefix = Path("model_params/vgg16_cifar10")
+  tune_set = CIFAR.from_file(prefix / "tune_input.bin", prefix / "tune_labels.bin")
+  tune_loader = DataLoader(tune_set, batch_size=500)
+  test_set = CIFAR.from_file(prefix / "test_input.bin", prefix / "test_labels.bin")
+  test_loader = DataLoader(test_set, batch_size=500)
+
+  module = VGG16Cifar10()
+  module.load_state_dict(torch.load("model_params/vgg16_cifar10.pth.tar"))
+
+PredTuner provides a logging mechanism.
+While not required, it's recommended that you set up the logger output into a file:
+
+.. code-block:: python
+
+  msg_logger = pt.config_pylogger(output_dir="vgg16_cifar10/", verbose=True)
+
+For each tuning task, both a tuning dataset and a test dataset is required.
+The tuning dataset is used to evaluate the accuracy of application in the autotuning stage,
+while the test dataset is used to evaluate configurations found in autotuning.
+This is similar to the split between training and validation set in machine learning tasks.
+In this case, both tuning and test datasets contain 5000 images.
+
+Create an instance of :code:`TorchApp` for tuning PyTorch DNN:
+
+.. code-block:: python
+
+  app = pt.TorchApp(
+    "TestTorchApp",  # Application name -- can be anything
+    module,
+    tune_loader,
+    test_loader,
+    knobs=pt.get_knobs_from_file(),
+    tensor_to_qos=pt.accuracy,
+    model_storage_folder="vgg16_cifar10/",
+  )
+
+PredTuner provides :code:`TorchApp`, which is specialized for the use scenario of tuning PyTorch DNNs.
+In addition, two more functions from PredTuner are used:
+
+:code:`pt.accuracy` is the *classification accuracy* metric,
+which receives the probability distribution output from the VGG16 model,
+compare it to the groundtruth in the dataset,
+and returns a scalar between 0 and 100 for the classification accuracy
+
+:code:`pt.get_knobs_from_file()` returns a set of approximations preloaded in PredTuner,
+which are applied to :code:`torch.nn.Conv2d` layers.
+See ??? for these approximations and how to define your own approximations.
+
+Now we can obtain a tuner object from the application and start tuning.
+We will keep configurations that don't exceed 3% loss of accuracy,
+but encourage the tuner to find configurations with loss of accuracy below 2.1%.
+
+.. code-block:: python
+
+  tuner = app.get_tuner()
+  tuner.tune(
+      max_iter=100,
+      qos_tuner_threshold=2.1,  # QoS threshold to guide tuner into
+      qos_keep_threshold=3.0,  # QoS threshold for which we actually keep the thresholds
+      is_threshold_relative=True,  # Thresholds are relative to baseline -- baseline_acc - 2.1
+      perf_model="perf_linear",  # Use linear performance predictor
+  )
+
+:code:`max_iter` defines the number of iterations to use in autotuning.
+100 iterations is for demonstration; in practice,
+at least 10000 iterations are necessary on VGG16-sized models to converge to a set of good configurations.
+
+Saving Tuning Results
+---------------------
+
+Now the :code:`tuner` object holds the tuning results,
+we can export it into a json file,
+and visualize all configurations in a figure:
+
+.. code-block:: python
+
+  tuner.dump_configs("vgg16_cifar10/configs.json", best_only=False)
+  fig = tuner.plot_configs(show_qos_loss=True)
+  fig.savefig("vgg16_cifar10/configs.png")
+
+PredTuner will also automatically mark out `Pareto-optimal
+<https://en.wikipedia.org/wiki/Pareto_efficiency>`_
+configurations.
+These are called "best" configurations (:code:`tuner.best_configs`),
+in contrast to "valid" configurations which are the configurations that satisfy our accuracy requirements
+(:code:`tuner.kept_configs`).
+
+Within 100 iterations, PredTuner should find 30~50 valid configurations.
+The generated figure should look like this:
+
+.. image:: tuning_result.png
+
+
+Loading Tuning Results
+----------------------
+
+TODO: TODO
+
diff --git a/doc/index.rst b/doc/index.rst
index 6866a98..b79d28a 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -9,27 +9,21 @@ PredTuner performs autotuning on approximation choices for a program
 using an error-predictive proxy instead of executing the program,
 to greatly speedup autotuning while getting results of comparable quality.
 
-PredTuner is a contribution of [ApproxTuner]
-(https://ppopp21.sigplan.org/details/PPoPP-2021-main-conference/41/ApproxTuner-A-Compiler-and-Runtime-System-for-Adaptive-Approximations).
-
-Short-term Goals
-- Measure accuracy impact of approximations
-- Obtain a tuned, approximated CNN in <5 lines of code
-- Easy to manage multiple approximation configs
-- Easy to load and manage prior tuning results
-- Flexible retraining support
-Possible Long-term Goals
-- High-performance implementations of approximate layers
-- Allow users to register their own approximations
-- Support for other frameworks: TF, ONNX, JAX
+PredTuner is a main component of `ApproxTuner
+<https://ppopp21.sigplan.org/details/PPoPP-2021-main-conference/41/ApproxTuner-A-Compiler-and-Runtime-System-for-Adaptive-Approximations>`_.
 
-Documentation
--------------
 
-.. only:: html
+Solution for Efficient Approximation Autotuning
+-----------------------------------------------
+
+- Start a tuning session in 10 lines of code
+- Deep integration with PyTorch for DNN supports
+- Multiple levels of APIs for generality and ease-of-use
+- Effective accuracy prediction models
+- Easily store and visualize tuning results in many formats
 
-    :Release: |version|
-    :Date: |today|
+Documentation
+-------------
 
 .. toctree::
    :maxdepth: 1
@@ -41,6 +35,3 @@ Indices and tables
 ------------------
 
 * :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-* :ref:`glossary`
diff --git a/doc/tuning_result.png b/doc/tuning_result.png
new file mode 100644
index 0000000000000000000000000000000000000000..d3854041d97ab51be43aa08e563f1850bba19f48
GIT binary patch
literal 25922
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
B!(;#e

literal 0
HcmV?d00001

diff --git a/examples/tune_vgg16_cifar10.py b/examples/tune_vgg16_cifar10.py
index eca7082..9530bc0 100644
--- a/examples/tune_vgg16_cifar10.py
+++ b/examples/tune_vgg16_cifar10.py
@@ -17,9 +17,9 @@ msg_logger = config_pylogger(output_dir="/tmp", verbose=True)
 # TODO: you should use all (5000) images for actual tuning.
 prefix = Path("model_params/vgg16_cifar10")
 tune_set = CIFAR.from_file(prefix / "tune_input.bin", prefix / "tune_labels.bin")
-tune_loader = DataLoader(Subset(tune_set, range(500)), batch_size=500)
+tune_loader = DataLoader(tune_set, batch_size=500)
 test_set = CIFAR.from_file(prefix / "test_input.bin", prefix / "test_labels.bin")
-test_loader = DataLoader(Subset(test_set, range(500)), batch_size=500)
+test_loader = DataLoader(test_set, batch_size=500)
 
 # Load checkpoint for VGG16 (CIFAR10)
 module = VGG16Cifar10()
@@ -38,17 +38,17 @@ app = TorchApp(
     model_storage_folder="tuner_results/vgg16_cifar10",
 )
 # This is how to measure baseline accuracy -- {} means no approximation
-baseline, _ = app.measure_qos_perf({}, False)
+baseline, _ = app.measure_qos_cost({}, False)
 # Get a tuner object and start tuning!
 tuner = app.get_tuner()
 tuner.tune(
-    max_iter=100,  # TODO: In practice, use at least 5000, or 10000
+    max_iter=500,  # TODO: In practice, use at least 5000, or 10000
     qos_tuner_threshold=2.1,  # QoS threshold to guide tuner into
     qos_keep_threshold=3.0,  # QoS threshold for which we actually keep the thresholds
     is_threshold_relative=True,  # Thresholds are relative to baseline -- baseline_acc - 2.1
-    take_best_n=50,  # Take 50 "best" configs
-    perf_model="perf_linear",  # Use linear performance predictor
-    qos_model="qos_p1",  # Use P1 QoS predictor
+    cost_model="cost_linear",  # Use linear performance predictor
 )
 # Save configs here when you're done
 tuner.dump_configs("tuner_results/vgg16_cifar10_configs.json")
+fig = tuner.plot_configs(show_qos_loss=True)
+fig.savefig("tuner_results/vgg16_cifar10_configs.png")
\ No newline at end of file
diff --git a/predtuner/approxapp.py b/predtuner/approxapp.py
index a6513cf..b34b1a1 100644
--- a/predtuner/approxapp.py
+++ b/predtuner/approxapp.py
@@ -222,7 +222,7 @@ class ApproxTuner(Generic[T]):
         taken_idx = is_pareto_efficient(points, take_n=n)
         return [configs[i] for i in taken_idx]
 
-    def dump_configs(self, filepath: PathLike):
+    def dump_configs(self, filepath: PathLike, best_only: bool = True):
         import os
 
         from jsonpickle import encode
@@ -233,8 +233,9 @@ class ApproxTuner(Generic[T]):
             )
         filepath = Path(filepath)
         os.makedirs(filepath.parent, exist_ok=True)
+        confs = self.best_configs if best_only else self.kept_configs
         with filepath.open("w") as f:
-            f.write(encode(self.best_configs, indent=2))
+            f.write(encode(confs, indent=2))
 
     def plot_configs(
         self, show_qos_loss: bool = False, connect_best_points: bool = False
-- 
GitLab