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