From f6d748d98853c46620025c83c4f09e6623f55375 Mon Sep 17 00:00:00 2001 From: kindaSys331 <80989051+kindaSys331@users.noreply.github.com> Date: Sat, 6 Aug 2022 01:05:15 +0300 Subject: [PATCH] Initial commit --- .gitignore | 106 +++++ .gradient/workflows/workflow.yaml | 34 ++ Dockerfile.cpu | 17 + Dockerfile.gpu | 18 + LICENSE | 21 + README.md | 196 +++++++- __init__.py | 0 dataset.py | 117 +++++ example3.png | Bin 0 -> 840 bytes example5.png | Bin 0 -> 812 bytes examples.npy | Bin 0 -> 12624 bytes inference-rest-client-test.ipynb | 108 +++++ mnist-sample.yaml | 27 ++ mnist.py | 275 +++++++++++ mnist_grpc_client.py | 149 ++++++ requirements-local.txt | 8 + requirements.txt | 22 + requirements_local_grpc.txt | 6 + serving_rest_client_test.py | 124 +++++ utils/__init__.py | 0 utils/accelerator/__init__.py | 0 utils/accelerator/tpu.py | 116 +++++ utils/accelerator/tpu_test.py | 108 +++++ utils/export/__init__.py | 0 utils/export/export.py | 49 ++ utils/export/export_test.py | 63 +++ utils/flags/README.md | 97 ++++ utils/flags/__init__.py | 0 utils/flags/_base.py | 156 +++++++ utils/flags/_benchmark.py | 99 ++++ utils/flags/_conventions.py | 46 ++ utils/flags/_device.py | 85 ++++ utils/flags/_misc.py | 50 ++ utils/flags/_performance.py | 184 ++++++++ utils/flags/core.py | 88 ++++ utils/flags/flags_test.py | 100 ++++ utils/flags/guidelines.md | 64 +++ utils/logs/__init__.py | 0 utils/logs/guidelines.md | 58 +++ utils/logs/hooks.py | 130 ++++++ utils/logs/hooks_helper.py | 165 +++++++ utils/logs/hooks_helper_test.py | 67 +++ utils/logs/hooks_test.py | 158 +++++++ utils/logs/logger.py | 442 ++++++++++++++++++ utils/logs/logger_test.py | 366 +++++++++++++++ utils/logs/metric_hook.py | 97 ++++ utils/logs/metric_hook_test.py | 217 +++++++++ utils/logs/mlperf_helper.py | 192 ++++++++ utils/misc/__init__.py | 0 utils/misc/distribution_utils.py | 91 ++++ utils/misc/distribution_utils_test.py | 65 +++ utils/misc/model_helpers.py | 93 ++++ utils/misc/model_helpers_test.py | 121 +++++ utils/testing/__init__.py | 0 utils/testing/integration.py | 65 +++ utils/testing/mock_lib.py | 36 ++ utils/testing/pylint.rcfile | 169 +++++++ utils/testing/reference_data.py | 336 +++++++++++++ .../reference_data_test/dense/expected_graph | Bin 0 -> 5996 bytes .../dense/model.ckpt.data-00000-of-00001 | Bin 0 -> 76 bytes .../dense/model.ckpt.index | Bin 0 -> 254 bytes .../reference_data_test/dense/results.json | 1 + .../reference_data_test/dense/tf_version.json | 1 + .../uniform_random/expected_graph | Bin 0 -> 893 bytes .../model.ckpt.data-00000-of-00001 | 1 + .../uniform_random/model.ckpt.index | Bin 0 -> 136 bytes .../uniform_random/results.json | 1 + .../uniform_random/tf_version.json | 1 + .../expected_graph | Bin 0 -> 27274 bytes .../model.ckpt.data-00000-of-00001 | Bin 0 -> 33456 bytes .../model.ckpt.index | Bin 0 -> 824 bytes .../results.json | 1 + .../tf_version.json | 1 + .../expected_graph | Bin 0 -> 22479 bytes .../model.ckpt.data-00000-of-00001 | Bin 0 -> 33264 bytes .../model.ckpt.index | Bin 0 -> 680 bytes .../results.json | 1 + .../tf_version.json | 1 + .../expected_graph | Bin 0 -> 20424 bytes .../model.ckpt.data-00000-of-00001 | Bin 0 -> 32932 bytes .../model.ckpt.index | Bin 0 -> 628 bytes .../results.json | 1 + .../tf_version.json | 1 + .../expected_graph | Bin 0 -> 20432 bytes .../model.ckpt.data-00000-of-00001 | Bin 0 -> 32932 bytes .../model.ckpt.index | Bin 0 -> 628 bytes .../results.json | 1 + .../tf_version.json | 1 + .../expected_graph | Bin 0 -> 20709 bytes .../model.ckpt.data-00000-of-00001 | Bin 0 -> 36736 bytes .../model.ckpt.index | Bin 0 -> 641 bytes .../results.json | 1 + .../tf_version.json | 1 + .../expected_graph | Bin 0 -> 15914 bytes .../model.ckpt.data-00000-of-00001 | Bin 0 -> 36544 bytes .../model.ckpt.index | Bin 0 -> 521 bytes .../results.json | 1 + .../tf_version.json | 1 + .../expected_graph | Bin 0 -> 13864 bytes .../model.ckpt.data-00000-of-00001 | Bin 0 -> 34048 bytes .../model.ckpt.index | Bin 0 -> 477 bytes .../results.json | 1 + .../tf_version.json | 1 + .../expected_graph | Bin 0 -> 13867 bytes .../model.ckpt.data-00000-of-00001 | Bin 0 -> 34048 bytes .../model.ckpt.index | Bin 0 -> 477 bytes .../results.json | 1 + .../tf_version.json | 1 + .../resnet/batch_norm/expected_graph | Bin 0 -> 5513 bytes .../batch_norm/model.ckpt.data-00000-of-00001 | Bin 0 -> 98352 bytes .../resnet/batch_norm/model.ckpt.index | Bin 0 -> 275 bytes .../resnet/batch_norm/results.json | 1 + .../resnet/batch_norm/tf_version.json | 1 + utils/testing/reference_data_test.py | 133 ++++++ utils/testing/scripts/presubmit.sh | 97 ++++ 115 files changed, 5653 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .gradient/workflows/workflow.yaml create mode 100644 Dockerfile.cpu create mode 100644 Dockerfile.gpu create mode 100644 LICENSE create mode 100644 __init__.py create mode 100644 dataset.py create mode 100644 example3.png create mode 100644 example5.png create mode 100644 examples.npy create mode 100644 inference-rest-client-test.ipynb create mode 100644 mnist-sample.yaml create mode 100644 mnist.py create mode 100644 mnist_grpc_client.py create mode 100644 requirements-local.txt create mode 100644 requirements.txt create mode 100644 requirements_local_grpc.txt create mode 100644 serving_rest_client_test.py create mode 100644 utils/__init__.py create mode 100644 utils/accelerator/__init__.py create mode 100644 utils/accelerator/tpu.py create mode 100644 utils/accelerator/tpu_test.py create mode 100644 utils/export/__init__.py create mode 100644 utils/export/export.py create mode 100644 utils/export/export_test.py create mode 100644 utils/flags/README.md create mode 100644 utils/flags/__init__.py create mode 100644 utils/flags/_base.py create mode 100644 utils/flags/_benchmark.py create mode 100644 utils/flags/_conventions.py create mode 100644 utils/flags/_device.py create mode 100644 utils/flags/_misc.py create mode 100644 utils/flags/_performance.py create mode 100644 utils/flags/core.py create mode 100644 utils/flags/flags_test.py create mode 100644 utils/flags/guidelines.md create mode 100644 utils/logs/__init__.py create mode 100644 utils/logs/guidelines.md create mode 100644 utils/logs/hooks.py create mode 100644 utils/logs/hooks_helper.py create mode 100644 utils/logs/hooks_helper_test.py create mode 100644 utils/logs/hooks_test.py create mode 100644 utils/logs/logger.py create mode 100644 utils/logs/logger_test.py create mode 100644 utils/logs/metric_hook.py create mode 100644 utils/logs/metric_hook_test.py create mode 100644 utils/logs/mlperf_helper.py create mode 100644 utils/misc/__init__.py create mode 100644 utils/misc/distribution_utils.py create mode 100644 utils/misc/distribution_utils_test.py create mode 100644 utils/misc/model_helpers.py create mode 100644 utils/misc/model_helpers_test.py create mode 100644 utils/testing/__init__.py create mode 100644 utils/testing/integration.py create mode 100644 utils/testing/mock_lib.py create mode 100644 utils/testing/pylint.rcfile create mode 100644 utils/testing/reference_data.py create mode 100644 utils/testing/reference_data/reference_data_test/dense/expected_graph create mode 100644 utils/testing/reference_data/reference_data_test/dense/model.ckpt.data-00000-of-00001 create mode 100644 utils/testing/reference_data/reference_data_test/dense/model.ckpt.index create mode 100644 utils/testing/reference_data/reference_data_test/dense/results.json create mode 100644 utils/testing/reference_data/reference_data_test/dense/tf_version.json create mode 100644 utils/testing/reference_data/reference_data_test/uniform_random/expected_graph create mode 100644 utils/testing/reference_data/reference_data_test/uniform_random/model.ckpt.data-00000-of-00001 create mode 100644 utils/testing/reference_data/reference_data_test/uniform_random/model.ckpt.index create mode 100644 utils/testing/reference_data/reference_data_test/uniform_random/results.json create mode 100644 utils/testing/reference_data/reference_data_test/uniform_random/tf_version.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-1_width-8_channels-4/expected_graph create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-1_width-8_channels-4/model.ckpt.data-00000-of-00001 create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-1_width-8_channels-4/model.ckpt.index create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-1_width-8_channels-4/results.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-1_width-8_channels-4/tf_version.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-2_width-8_channels-4/expected_graph create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-2_width-8_channels-4/model.ckpt.data-00000-of-00001 create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-2_width-8_channels-4/model.ckpt.index create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-2_width-8_channels-4/results.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-2_width-8_channels-4/tf_version.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/expected_graph create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/model.ckpt.data-00000-of-00001 create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/model.ckpt.index create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/results.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/tf_version.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/expected_graph create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/model.ckpt.data-00000-of-00001 create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/model.ckpt.index create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/results.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/tf_version.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_projection_version-1_width-8_channels-4/expected_graph create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_projection_version-1_width-8_channels-4/model.ckpt.data-00000-of-00001 create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_projection_version-1_width-8_channels-4/model.ckpt.index create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_projection_version-1_width-8_channels-4/results.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_projection_version-1_width-8_channels-4/tf_version.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_projection_version-2_width-8_channels-4/expected_graph create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_projection_version-2_width-8_channels-4/model.ckpt.data-00000-of-00001 create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_projection_version-2_width-8_channels-4/model.ckpt.index create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_projection_version-2_width-8_channels-4/results.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_projection_version-2_width-8_channels-4/tf_version.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_version-1_width-8_channels-4/expected_graph create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_version-1_width-8_channels-4/model.ckpt.data-00000-of-00001 create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_version-1_width-8_channels-4/model.ckpt.index create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_version-1_width-8_channels-4/results.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_version-1_width-8_channels-4/tf_version.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_version-2_width-8_channels-4/expected_graph create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_version-2_width-8_channels-4/model.ckpt.data-00000-of-00001 create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_version-2_width-8_channels-4/model.ckpt.index create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_version-2_width-8_channels-4/results.json create mode 100644 utils/testing/reference_data/resnet/batch-size-32_building_version-2_width-8_channels-4/tf_version.json create mode 100644 utils/testing/reference_data/resnet/batch_norm/expected_graph create mode 100644 utils/testing/reference_data/resnet/batch_norm/model.ckpt.data-00000-of-00001 create mode 100644 utils/testing/reference_data/resnet/batch_norm/model.ckpt.index create mode 100644 utils/testing/reference_data/resnet/batch_norm/results.json create mode 100644 utils/testing/reference_data/resnet/batch_norm/tf_version.json create mode 100644 utils/testing/reference_data_test.py create mode 100755 utils/testing/scripts/presubmit.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ad6467 --- /dev/null +++ b/.gitignore @@ -0,0 +1,106 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +.idea/ \ No newline at end of file diff --git a/.gradient/workflows/workflow.yaml b/.gradient/workflows/workflow.yaml new file mode 100644 index 0000000..d022d2e --- /dev/null +++ b/.gradient/workflows/workflow.yaml @@ -0,0 +1,34 @@ +on: + github: + branches: + only: main + +jobs: + CloneRepo: + resources: + instance-type: C3 + uses: git-checkout@v1 + with: + # url: https://github.com/gradient-ai/mnist-sample.git + url: context.event.github.url + ref: context.event.github.ref + outputs: + mnist-sample: + type: volume + TrainModel: + resources: + instance-type: C3 + env: + PS_MODEL_PATH: /my-trained-model + uses: container@v1 + with: + args: + - bash + - -c + - >- + cd /inputs/mnist-sample && python mnist.py + image: tensorflow/tensorflow:1.15.5-py3 + needs: + - CloneRepo + inputs: + mnist-sample: CloneRepo.outputs.mnist-sample diff --git a/Dockerfile.cpu b/Dockerfile.cpu new file mode 100644 index 0000000..e40755c --- /dev/null +++ b/Dockerfile.cpu @@ -0,0 +1,17 @@ +# Docker image for running examples in Tensorflow models. +# base_image depends on whether we are running on GPUs or non-GPUs +FROM ubuntu:latest + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + build-essential \ + git \ + python \ + python-pip \ + python-setuptools + +RUN pip install tf-nightly + +# Checkout tensorflow/models at HEAD +RUN git clone https://github.com/tensorflow/models.git /tensorflow_models + diff --git a/Dockerfile.gpu b/Dockerfile.gpu new file mode 100644 index 0000000..012b49c --- /dev/null +++ b/Dockerfile.gpu @@ -0,0 +1,18 @@ +# Docker image for running examples in Tensorflow models. +# base_image depends on whether we are running on GPUs or non-GPUs +FROM nvidia/cuda:9.0-cudnn7-runtime-ubuntu16.04 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + build-essential \ + git \ + python \ + python-pip \ + python-setuptools + +RUN pip install tf-nightly-gpu + +# Checkout tensorflow/models at HEAD +RUN git clone https://github.com/tensorflow/models.git /tensorflow_models + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4631289 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Paperspace + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 97601e0..714f9c4 100644 --- a/README.md +++ b/README.md @@ -1 +1,195 @@ -# MNIST \ No newline at end of file +# MNIST in TensorFlow + +This repository demonstrates using Paperspace Gradient to train and deploy a deep learning model to recognize handwritten characters, which is a canonical sample problem in machine learning. + +We build a convolutional neural network to classify the [MNIST +dataset](http://yann.lecun.com/exdb/mnist/) using the +[tf.data](https://www.tensorflow.org/api_docs/python/tf/data), +[tf.estimator.Estimator](https://www.tensorflow.org/api_docs/python/tf/estimator/Estimator), +and +[tf.layers](https://www.tensorflow.org/versions/r1.15/api_docs/python/tf) +APIs. + +# Gradient Setup + +## Single Node Training on Gradient + +### Install Gradient CLI + +``` +pip install -U gradient +``` + +[Please check our documentation on how to install Gradient CLI and obtain an API Key](https://docs.paperspace.com/gradient/get-started/install-the-cli) + +### Create project and get the project id + +[Please check our documentation on how to create a project and get the project id](https://docs.paperspace.com/gradient/get-started/managing-projects) +Your project ID will look like `pr1234567`. + +### Create and start a workflow + +``` +gradient workflows create --name mnist-sample --projectId pr1234567 ++--------------+--------------------------------------+ +| Name | ID | ++--------------+--------------------------------------+ +| mnist-sample | 12345678-1234-1234-1234-1234567890ab | ++--------------+--------------------------------------+ + +``` + +Clone this repo, and change directoru into it, or copy [mnist-sample.yaml](mnist-sample.yaml) to your local machine. + +Then run the workflow using the workflow ID from the create workflow command above. + +``` +gradient workflows run --id 12345678-1234-1234-1234-1234567890ab --path mnist-sample.yaml +``` + +That's it! + +### Exporting a Model for inference + +#### Export your Tensorflow model + +In order to serve a Tensorflow model, simply export a SavedModel from your Tensorflow program. [SavedModel](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/saved_model/README.md) is a language-neutral, recoverable, hermetic serialization format that enables higher-level systems and tools to produce, consume, and transform TensorFlow models. + +Please refer to [Tensorflow documentation](https://www.tensorflow.org/guide/saved_model#save_and_restore_models) for detailed instructions on how to export SavedModels. + +#### Example code showing how to export your model: + +``` +tf.estimator.train_and_evaluate(mnist_classifier, train_spec, eval_spec) + +#Starting to Export model +image = tf.placeholder(tf.float32, [None, 28, 28]) +input_fn = tf.estimator.export.build_raw_serving_input_receiver_fn({ + 'image': image, + }) +mnist_classifier.export_savedmodel(, + input_fn, + strip_default_attrs=True) +#Model Exported +``` + +We use TensorFlow's [SavedModelBuilder module](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/saved_model/builder.py) to export the model. SavedModelBuilder saves a "snapshot" of the trained model to reliable storage so that it can be loaded later for inference. + +For details on the SavedModel format, please see the documentation at [SavedModel README.md](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/saved_model/README.md). + +For export directory, be sure to set it to `PS_MODEL_PATH` when running a model deployment on Gradient: + +``` +export_dir = os.path.abspath(os.environ.get('PS_MODEL_PATH')) +``` + +You can also use Gradient SDK to ensure you have the correct path: + +``` +from gradient_sdk.utils import data_dir, model_dir, export_dir +``` + +# (Optional) Local Setup using a Virtual Environment + +Users sometimes run into local machine environment issues when trying to use Python. A common solution for this is to create and use a Python virtual environment to run Python from within. To do so: + +1. Create and activate a Python virtual environment (we recommend using python3.7+): + +``` +cd mnist-sample + +python3 -m venv venv + +source venv/bin/activate +``` + +2. Install the required Python packages: + +``` +pip install -r requirements-local.txt +``` + +# Local Training + +To train a the mnist model locally: + +1. Make sure you have the latest version of TensorFlow installed. + +2. Also make sure you've [added the models folder to your Python path](https://github.com/mlcommons/training/blob/master/image_classification/tensorflow/official/README.md#running-the-models); otherwise you may encounter an error like `ImportError: No module named mnist`. + +3. Download the code from GitHub: + +``` +git clone git@github.com:Paperspace/mnist-sample.git +``` + +4. Start training the model: + +``` +python mnist.py +``` + +_Note: local training will take a long time, so be prepared to wait!_ + +If you want to shorten model training time, you can change the max steps parameter: + +``` +python mnist.py --max_steps=1500 +``` + +The mnist dataset is downloaded to the `./data` directory. + +Model results are stored in the `./models` directory. + +Both directories can be safely deleted if you would like to start the training over from the beginning. + +## Exporting the model to a specific directory + +You can export the model into a specific directory, in the Tensorflow [SavedModel](https://www.tensorflow.org/guide/saved_model) format, by using the argument `--export_dir`: + +``` +python mnist.py --export_dir /tmp/mnist_saved_model +``` + +If no export directory is specified, the model is saved to a timestamped directory under `./models` subdirectory (e.g. `mnist-sample/models/1513630966/`). + +## Testing a Tensorflow Serving-deployed model on your local machine using Docker + +Open another terminal window and run the following in the directory where you cloned this repo: + +``` +docker run -t --rm -p 8501:8501 -v "$PWD/models:/models/mnist" -e MODEL_NAME=mnist tensorflow/serving +``` + +Now you can test the local inference endpoint by running: + +``` +python serving_rest_client_test.py +``` + +Optionally you can provide a path to an image file to run a prediction on: + +``` +python serving_rest_client_test.py --path example3.png +``` + +Once you've completed local testing using the tensorflow/serving docker container, stop the running container as follows: + +``` +docker ps +docker kill +``` + +## Training the model on a node with a GPU for use with Tensorflow Serving on a node with only a CPU + +If you are training on Tensorflow using a GPU but would like to export the model for use in Tensorflow Serving on a CPU-only server, you can train and/or export the model using `--data_format=channels_last`: + +``` +python mnist.py --data_format=channels_last +``` + +The SavedModel will be saved in a timestamped directory under `models` subdirectory (e.g. `mnist-sample/models/1513630966/`). + +## Inspecting and getting predictions with the SavedModel file + +You can also use the [`saved_model_cli`](https://www.tensorflow.org/guide/saved_model#cli_to_inspect_and_execute_savedmodel) tool to inspect and execute the SavedModel. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dataset.py b/dataset.py new file mode 100644 index 0000000..6b12d7e --- /dev/null +++ b/dataset.py @@ -0,0 +1,117 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""tf.data.Dataset interface to the MNIST dataset.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import gzip +import os +import shutil +import tempfile + +import numpy as np +from six.moves import urllib +import tensorflow as tf + + +def read32(bytestream): + """Read 4 bytes from bytestream as an unsigned 32-bit integer.""" + dt = np.dtype(np.uint32).newbyteorder('>') + return np.frombuffer(bytestream.read(4), dtype=dt)[0] + + +def check_image_file_header(filename): + """Validate that filename corresponds to images for the MNIST dataset.""" + with tf.gfile.Open(filename, 'rb') as f: + magic = read32(f) + read32(f) # num_images, unused + rows = read32(f) + cols = read32(f) + if magic != 2051: + raise ValueError('Invalid magic number %d in MNIST file %s' % (magic, + f.name)) + if rows != 28 or cols != 28: + raise ValueError( + 'Invalid MNIST file %s: Expected 28x28 images, found %dx%d' % + (f.name, rows, cols)) + + +def check_labels_file_header(filename): + """Validate that filename corresponds to labels for the MNIST dataset.""" + with tf.gfile.Open(filename, 'rb') as f: + magic = read32(f) + read32(f) # num_items, unused + if magic != 2049: + raise ValueError('Invalid magic number %d in MNIST file %s' % (magic, + f.name)) + + +def download(directory, filename): + """Download (and unzip) a file from the MNIST dataset if not already done.""" + filepath = os.path.join(directory, filename) + if tf.gfile.Exists(filepath): + return filepath + if not tf.gfile.Exists(directory): + tf.gfile.MakeDirs(directory) + # CVDF mirror of http://yann.lecun.com/exdb/mnist/ + url = 'https://storage.googleapis.com/cvdf-datasets/mnist/' + filename + '.gz' + _, zipped_filepath = tempfile.mkstemp(suffix='.gz') + print('Downloading %s to %s' % (url, zipped_filepath)) + urllib.request.urlretrieve(url, zipped_filepath) + with gzip.open(zipped_filepath, 'rb') as f_in, \ + tf.gfile.Open(filepath, 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + os.remove(zipped_filepath) + return filepath + + +def dataset(directory, images_file, labels_file): + """Download and parse MNIST dataset.""" + + images_file = download(directory, images_file) + labels_file = download(directory, labels_file) + + check_image_file_header(images_file) + check_labels_file_header(labels_file) + + def decode_image(image): + # Normalize from [0, 255] to [0.0, 1.0] + image = tf.decode_raw(image, tf.uint8) + image = tf.cast(image, tf.float32) + image = tf.reshape(image, [784]) + return image / 255.0 + + def decode_label(label): + label = tf.decode_raw(label, tf.uint8) # tf.string -> [tf.uint8] + label = tf.reshape(label, []) # label is a scalar + return tf.cast(label, tf.int32) + + images = tf.data.FixedLengthRecordDataset( + images_file, 28 * 28, header_bytes=16).map(decode_image) + labels = tf.data.FixedLengthRecordDataset( + labels_file, 1, header_bytes=8).map(decode_label) + return tf.data.Dataset.zip((images, labels)) + + +def train(directory): + """tf.data.Dataset object for MNIST training data.""" + return dataset(directory, 'train-images-idx3-ubyte', + 'train-labels-idx1-ubyte') + + +def test(directory): + """tf.data.Dataset object for MNIST test data.""" + return dataset(directory, 't10k-images-idx3-ubyte', 't10k-labels-idx1-ubyte') diff --git a/example3.png b/example3.png new file mode 100644 index 0000000000000000000000000000000000000000..d03fc4bb4b29143813d3c512e69b91f22d4b4c0d GIT binary patch literal 840 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*s1|*Ak?@s|zEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5n0T@AlnYYjQvkP zrZO-vMrMXYltlRYSS9D@>LsS+C#C9DVstT4fPE4;bsH1+JHo@{EISEfi{E8 zw==W>t3(ll+GC>+vK+}V5TAlYfnK%aveAbJn;nM%Tad_Y9(dx?rYLcgJ2{s5^rj_+XD#}UO+S!)pmLt9Kw{*y zow0>nGi=zFIBN1RhNw8~l{_dg&GcvJdedERgf71g`|Wc@tTa|%F82D{c?Qk@D>N}*dFG0ChziG=8ylAn3I%b&gD;vb|~R;)V*WQwgsgI?e6-8GD-=H*_LJG{CVlwLhu{an^L HB{Ts5{&Fp; literal 0 HcmV?d00001 diff --git a/example5.png b/example5.png new file mode 100644 index 0000000000000000000000000000000000000000..93d351dee28c2f697361305fc4ff34c4bb46618b GIT binary patch literal 812 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*s1|*Ak?@s|zEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5n0T@AlnYYjQvkP zrZO-vMrMXYltlRYSS9D@>LsS+C#C9DVstT4fPE4;bsH1+JHo@{EISEfi{E8 zw==W>t3(ll+GC>+vK+}V5TAlYfnK%aveAbJn;nhy<)sy?d}%dl)(kXTcoHIj`{#EQq6r|qjnL2US{>$ll^ z5@q>c2tDwtjhD$TtG+kKds+38DJ2!V{`c6p{K($U_4d`;r^}iPkDohVb#T_=atW{Y zUF+5wuj4b?Hg$6Ad?qfNSIYLa&*$#6*zkInEPEzHgSPMGBMS-@K5;Xgu;>fhmzXq)>iLYOz zF0jA;eXsg>Uw41woaLN>r#77VbH>mhB+_TXoEF9^9v7wdCr6|+)&y5%n`xcUd?I=N evaG+#_7BYGi+q__(l+OU(xj)WpUXO@geCwyuq9Ri literal 0 HcmV?d00001 diff --git a/examples.npy b/examples.npy new file mode 100644 index 0000000000000000000000000000000000000000..85d78b1b6dadb1df44128ca173426aff9866c2c0 GIT binary patch literal 12624 zcmeHMy>8S%5H>AU1Sp9Rg*y}Js#Fm|a+eN7LqUa3dUF?)v*?XJ>XU&+p#9bML{WXft}2Y>g(5 z#>uTXxxKxS4B}*acl>lbeDZL2yfqU2o5P*SDDR&<9`23u_RaM{yuKm+)&}u@EaY+| zB}+poG!XOKI(_Fsy z?*D%O67q$OzloFDx3VncZSTAYQ7jl|xg#*e;uY`%@ii%L+V3I7ysKA)ysqg7cH8&< zncttD3i-;{yB5!(9kYZV^RLwXipdLY|9BxTkR86L*{5#r{_x}a7gzEJvLL&DxxB7R zdCxz;5`g{tT$~s;ue_-ApPHYUKVIPGmfZUZtyh1#$_xDU_*=){)>HZo$My@nLMb%R z|M>~~5cVP1;s1LM_4{S@yDs7;-uvbUxc4yP6-B=Aos}2(p(LL8ox>fww&icQht0;{ z9e!=U&x6- + cd /inputs/mnist-sample && python mnist.py + image: tensorflow/tensorflow:1.15.5-py3 + needs: + - CloneRepo + inputs: + mnist-sample: CloneRepo.outputs.mnist-sample diff --git a/mnist.py b/mnist.py new file mode 100644 index 0000000..9dce0ff --- /dev/null +++ b/mnist.py @@ -0,0 +1,275 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Convolutional Neural Network Estimator for MNIST, built with tf.layers.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os + +from absl import app as absl_app +from absl import flags +import tensorflow as tf # pylint: disable=g-bad-import-order + +gradient_sdk = True +try: + from gradient_sdk import get_tf_config +except ImportError: + print("Gradient SDK not installed. Distributed training is not possible") + gradient_sdk = False + + +import dataset +from utils.flags import core as flags_core +from utils.logs import hooks_helper +from utils.misc import distribution_utils +from utils.misc import model_helpers + +LEARNING_RATE = 1e-4 +FLAGS = flags.FLAGS + + +def create_model(data_format): + """Model to recognize digits in the MNIST dataset. + uses the tf.keras API. + Args: + data_format: Either 'channels_first' or 'channels_last'. 'channels_first' is + typically faster on GPUs while 'channels_last' is typically faster on + CPUs. See + https://www.tensorflow.org/performance/performance_guide#data_formats + Returns: + A tf.keras.Model. + """ + if data_format == 'channels_first': + input_shape = [1, 28, 28] + else: + assert data_format == 'channels_last' + input_shape = [28, 28, 1] + + l = tf.keras.layers + max_pool = l.MaxPooling2D( + (2, 2), (2, 2), padding='same', data_format=data_format) + # The model consists of a sequential chain of layers, so tf.keras.Sequential + # (a subclass of tf.keras.Model) makes for a compact description. + return tf.keras.Sequential( + [ + l.Reshape( + target_shape=input_shape, + input_shape=(28 * 28,)), + l.Conv2D( + 32, + 5, + padding='same', + data_format=data_format, + activation=tf.nn.relu), + max_pool, + l.Conv2D( + 64, + 5, + padding='same', + data_format=data_format, + activation=tf.nn.relu), + max_pool, + l.Flatten(), + l.Dense(1024, activation=tf.nn.relu), + l.Dropout(0.4), + l.Dense(10) + ]) + + +def define_mnist_flags(): + flags.DEFINE_integer('eval_secs', os.environ.get('EVAL_SECS', 600), 'How frequently to run evaluation step') + flags.DEFINE_integer('ckpt_steps', os.environ.get('CKPT_STEPS', 600), 'How frequently to save a model checkpoint') + flags.DEFINE_integer('max_ckpts', 5, 'Maximum number of checkpoints to keep') + flags.DEFINE_integer('max_steps', os.environ.get('MAX_STEPS', 100), 'Max steps') + flags.DEFINE_integer('save_summary_steps', 100, 'How frequently to save TensorBoard summaries') + flags.DEFINE_integer('log_step_count_steps', 100, 'How frequently to log loss & global steps/s') + flags_core.define_base() + flags_core.define_performance(num_parallel_calls=False) + flags_core.define_image() + data_dir = os.path.abspath(os.environ.get('PS_JOBSPACE', os.getcwd()) + '/data') + model_dir = os.path.abspath(os.environ.get('PS_MODEL_PATH', os.getcwd() + '/models') + '/mnist') + export_dir = os.path.abspath(os.environ.get('PS_MODEL_PATH', os.getcwd() + '/models')) + flags.adopt_module_key_flags(flags_core) + flags_core.set_defaults(data_dir=data_dir, + model_dir=model_dir, + export_dir=export_dir, + train_epochs=int(os.environ.get('TRAIN_EPOCHS', 5)), + epochs_between_evals=int(os.environ.get('EPOCHS_EVAL', 5)), + batch_size=int(os.environ.get('BATCH_SIZE', 100)), + ) + + +def model_fn(features, labels, mode, params): + """The model_fn argument for creating an Estimator.""" + model = create_model(params['data_format']) + image = features + if isinstance(image, dict): + image = features['image'] + + if mode == tf.estimator.ModeKeys.PREDICT: + logits = model(image, training=False) + predictions = { + 'classes': tf.argmax(logits, axis=1), + 'probabilities': tf.nn.softmax(logits), + } + return tf.estimator.EstimatorSpec( + mode=tf.estimator.ModeKeys.PREDICT, + predictions=predictions, + export_outputs={ + 'classify': tf.estimator.export.PredictOutput(predictions) + }) + if mode == tf.estimator.ModeKeys.TRAIN: + optimizer = tf.train.AdamOptimizer(learning_rate=LEARNING_RATE) + + logits = model(image, training=True) + loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits) + accuracy = tf.metrics.accuracy( + labels=labels, predictions=tf.argmax(logits, axis=1)) + + # Name tensors to be logged with LoggingTensorHook. + tf.identity(LEARNING_RATE, 'learning_rate') + tf.identity(loss, 'cross_entropy') + tf.identity(accuracy[1], name='train_accuracy') + + # Save accuracy scalar to Tensorboard output. + tf.summary.scalar('train_accuracy', accuracy[1]) + + tf.summary.scalar('loss', loss) + + return tf.estimator.EstimatorSpec( + mode=tf.estimator.ModeKeys.TRAIN, + loss=loss, + train_op=optimizer.minimize(loss, tf.train.get_or_create_global_step())) + if mode == tf.estimator.ModeKeys.EVAL: + logits = model(image, training=False) + loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits) + + tf.summary.scalar('eval_loss', loss) + + return tf.estimator.EstimatorSpec( + mode=tf.estimator.ModeKeys.EVAL, + loss=loss, + eval_metric_ops={ + 'accuracy': + tf.metrics.accuracy( + labels=labels, predictions=tf.argmax(logits, axis=1)), + }) + + +def run_mnist(flags_obj): + """Run MNIST training and eval loop. + Args: + flags_obj: An object containing parsed flag values. + """ + model_helpers.apply_clean(flags_obj) + model_function = model_fn + + session_config = tf.ConfigProto( + inter_op_parallelism_threads=flags_obj.inter_op_parallelism_threads, + intra_op_parallelism_threads=flags_obj.intra_op_parallelism_threads, + allow_soft_placement=True) + + distribution_strategy = distribution_utils.get_distribution_strategy( + flags_core.get_num_gpus(flags_obj), flags_obj.all_reduce_alg) + + run_config = tf.estimator.RunConfig( + train_distribute=distribution_strategy, + session_config=session_config, + save_checkpoints_steps=flags_obj.ckpt_steps, + keep_checkpoint_max=flags_obj.max_ckpts, + save_summary_steps=flags_obj.save_summary_steps, + log_step_count_steps=flags_obj.log_step_count_steps + ) + + data_format = flags_obj.data_format + if data_format is None: + data_format = ('channels_first' + if tf.test.is_built_with_cuda() else 'channels_last') + mnist_classifier = tf.estimator.Estimator( + model_fn=model_function, + model_dir=flags_obj.model_dir, + config=run_config, + params={ + 'data_format': data_format, + }) + + # Set up training and evaluation input functions. + def train_input_fn(): + """Prepare data for training.""" + + # When choosing shuffle buffer sizes, larger sizes result in better + # randomness, while smaller sizes use less memory. MNIST is a small + # enough dataset that we can easily shuffle the full epoch. + ds = dataset.train(flags_obj.data_dir) + ds = ds.cache().shuffle(buffer_size=50000).batch(flags_obj.batch_size) + + # Iterate through the dataset a set number (`epochs_between_evals`) of times + # during each training session. + ds = ds.repeat(flags_obj.epochs_between_evals) + return ds + + def eval_input_fn(): + return dataset.test(flags_obj.data_dir).batch( + flags_obj.batch_size).make_one_shot_iterator().get_next() + + # Set up hook that outputs training logs every 100 steps. + train_hooks = hooks_helper.get_train_hooks( + flags_obj.hooks, model_dir=flags_obj.model_dir, + batch_size=flags_obj.batch_size) + + train_spec = tf.estimator.TrainSpec(input_fn=train_input_fn, hooks=train_hooks, max_steps=flags_obj.max_steps) + eval_spec = tf.estimator.EvalSpec(input_fn=eval_input_fn, steps=None, + start_delay_secs=10, + throttle_secs=flags_obj.eval_secs) + + tf.estimator.train_and_evaluate(mnist_classifier, train_spec, eval_spec) + + # Export the model if node is master and export_dir is set and if experiment is multinode - check if its master + if os.environ.get('PS_CONFIG') and os.environ.get('TYPE') != 'master': + tf.logging.debug('No model was exported') + return + + if flags_obj.export_dir: + tf.logging.debug('Starting to Export model to {}'.format(str(flags_obj.export_dir))) + image = tf.placeholder(tf.float32, [None, 28, 28]) + input_fn = tf.estimator.export.build_raw_serving_input_receiver_fn({ + 'image': image, + }) + mnist_classifier.export_savedmodel(flags_obj.export_dir, input_fn, + strip_default_attrs=True) + tf.logging.debug('Model Exported') + + +def main(_): + run_mnist(flags.FLAGS) + + +if __name__ == '__main__': + + tf.logging.set_verbosity(tf.logging.DEBUG) + + if gradient_sdk: + try: + get_tf_config() + except: + pass + define_mnist_flags() + # Print ENV Variables + tf.logging.debug('=' * 20 + ' Environment Variables ' + '=' * 20) + for k, v in os.environ.items(): + tf.logging.debug('{}: {}'.format(k, v)) + + absl_app.run(main) diff --git a/mnist_grpc_client.py b/mnist_grpc_client.py new file mode 100644 index 0000000..149a83a --- /dev/null +++ b/mnist_grpc_client.py @@ -0,0 +1,149 @@ +# Simple TFServing example; Based on +# https://github.com/tensorflow/serving/blob/master/tensorflow_serving/example/mnist_client.py +# Added simpler mnist loading parts and removed some complexity + + +"""A client that talks to tensorflow_model_server loaded with mnist model. +The client downloads test images of mnist data set, queries the service with +such test images to get predictions, and calculates the inference error rate. +Typical usage example: + mnist_client.py --num_tests=100 --server=localhost:8500 +""" + +from __future__ import print_function + +import sys +import threading + +# This is a placeholder for a Google-internal import. + +import grpc +import numpy +import tensorflow as tf +import logging +from tensorflow_serving.apis import predict_pb2 +from tensorflow_serving.apis import prediction_service_pb2_grpc +from keras.preprocessing import image +import numpy as np +from keras.datasets import mnist +import time +from random import randint + +logging.disable(logging.WARNING) +tf.app.flags.DEFINE_integer('concurrency', 1, + 'maximum number of concurrent inference requests') +tf.app.flags.DEFINE_integer('num_tests', 1, 'Number of test images') +tf.app.flags.DEFINE_string('server', '', 'PredictionService host:port') +tf.app.flags.DEFINE_string('work_dir', '/tmp', 'Working directory') +tf.app.flags.DEFINE_string('model_name', 'mnist', 'Model name; specify "model" for Gradient') +tf.app.flags.DEFINE_string('hostname_override', None, 'Hostname override') +tf.app.flags.DEFINE_bool('insecure', False, 'Use insecure channel') +FLAGS = tf.app.flags.FLAGS + +_counter = 0 +_start = 0 +def _callback(result_future): + """Callback function. + Calculates the statistics for the prediction result. + Args: + result_future: Result future of the RPC. + """ + global _counter + global _start + exception = result_future.exception() + if exception: + print(exception) + else: + #print("From Callback",result_future.result().outputs['probabilities']) + if(_start == 0): + _start = time.time() + response = numpy.array( + result_future.result().outputs['probabilities'].float_val) + if response.size: + prediction = numpy.argmax(response) + if( (_counter % 10) ==0): + print("[", _counter,"] From Callback Predicted Result is ", prediction,"confidence= ",response[prediction]) + else: + print("empty response") + _counter += 1 + if (_counter == FLAGS.num_tests): + end = time.time() + print("Time for ",FLAGS.num_tests," is ",end -_start) + + +def do_inference(hostport, work_dir, concurrency, num_tests, model_name, hostname_override, insecure): + """Tests PredictionService with concurrent requests. + Args: + hostport: Host:port address of the PredictionService. + work_dir: The full path of working directory for test data set. + concurrency: Maximum number of concurrent requests. + num_tests: Number of test images to use. + Returns: + The classification error rate. + Raises: + IOError: An error occurred processing test data set. + """ + if insecure: + channel_options=None + if hostname_override: + channel_options=(('grpc.default_authority', hostname_overrride),) + channel = grpc.insecure_channel(hostport, options=channel_options) + else: + channel_options=None + if hostname_override: + channel_options=(('grpc.ssl_target_name_override', hostname_override),) + channel_creds = grpc.ssl_channel_credentials() + channel = grpc.secure_channel(hostport, channel_creds, options=channel_options) + + stub = prediction_service_pb2_grpc.PredictionServiceStub(channel) + request = predict_pb2.PredictRequest() + request.model_spec.name = model_name + request.model_spec.signature_name = 'serving_default' + + (X_train, y_train), (X_test, y_test) = mnist.load_data() + X_test = X_test.reshape(X_test.shape[0], 1, 28, 28) + X_train = X_train.reshape(X_train.shape[0], 1, 28, 28) + # For loading images + # img = image.load_img('./data/mnist_png/testing/0/10.png', target_size=(28,28)) + #x = image.img_to_array(img) + #request.inputs['images'].CopyFrom( + #tf.contrib.util.make_tensor_proto(image2, shape=[1,1,image2.size])) + image_index=randint(0, 9999) + x= X_train[image_index][0] + print("Shape is ",x.shape," Label is ", y_train[image_index]) + start = time.time() + for _ in range(num_tests): + xt= x.astype(np.float32) + request.inputs['image'].CopyFrom(tf.contrib.util.make_tensor_proto(xt, shape=[1,1,28, 28])) + #result_counter.throttle() + result_future = stub.Predict.future(request, 10.25) # 5 seconds; increase if above 1000 iterations + result_future.add_done_callback(_callback) + end = time.time() + image_index=randint(0, 9999) + x= X_train[image_index][0] + print("Time to Send ", num_tests ," is ",end - start) + + time.sleep(6) # increase if above 1000 iterations + # if things are wrong the callback will not come, so uncomment below to force the result + # or get to see what is the bug + #res= result_future.result() + #response = numpy.array(res.outputs['probabilities'].float_val) + #prediction = numpy.argmax(response) + #print("Predicted Result is ", prediction,"Detection Probability= ",response[prediction]) + + +def main(_): + if FLAGS.num_tests > 20000: + print('num_tests should not be greater than 20k') + return + if not FLAGS.server: + print('please specify server host:port') + return + error_rate = do_inference(FLAGS.server, FLAGS.work_dir, + FLAGS.concurrency, FLAGS.num_tests, + FLAGS.model_name, FLAGS.hostname_override, FLAGS.insecure) + +if __name__ == '__main__': + print ("hello from TFServing client slim") + tf.compat.v1.app.run() + diff --git a/requirements-local.txt b/requirements-local.txt new file mode 100644 index 0000000..4cdb4b5 --- /dev/null +++ b/requirements-local.txt @@ -0,0 +1,8 @@ +numpy>=1.15.4 +pandas>=0.22.0 +scipy>=0.19.1 +typing +gradient-sdk +Pillow>=6.0.0 +requests>=2.22.0 +tensorflow==1.13.1 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ac3f910 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,22 @@ +certifi==2020.12.5 +chardet==4.0.0 +decorator==4.3.2 +future==0.18.2 +gradient-sdk==0.0.4 +hyperopt==0.1.2 +idna==2.10 +networkx==2.2 +numpy==1.16.6 +pandas==0.24.2 +Pillow==6.2.2 +py4j==0.10.9 +pymongo==3.11.3 +pyspark==3.0.2 +python-dateutil==2.8.1 +pytz==2021.1 +requests==2.25.1 +scipy==1.2.3 +six==1.15.0 +tqdm==4.60.0 +typing==3.7.4.3 +urllib3==1.26.4 diff --git a/requirements_local_grpc.txt b/requirements_local_grpc.txt new file mode 100644 index 0000000..12b96e2 --- /dev/null +++ b/requirements_local_grpc.txt @@ -0,0 +1,6 @@ +numpy +tensorflow +keras +grpcio +setuptools>=41.0 +tensorflow-serving-api diff --git a/serving_rest_client_test.py b/serving_rest_client_test.py new file mode 100644 index 0000000..6927310 --- /dev/null +++ b/serving_rest_client_test.py @@ -0,0 +1,124 @@ +from random import randint + +import numpy as np +from PIL import Image as pilimage +import requests +import tensorflow as tf +import argparse +import getpass + + +def str2bool(v): + if isinstance(v, bool): + return v + if v.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif v.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise argparse.ArgumentTypeError('Boolean value expected.') + +def try_importing_mathplotlib(): + plotting = False + try: + import matplotlib + plotting = True + except ImportError: + print('Matplotlib not detected - images plotting not available') + return plotting + + +def get_image_from_drive(path): + # Load the image + image = pilimage.open(path) + image = image.convert('L') + image = np.resize(image, (28,28,1)) + image = np.array(image) + image = image.reshape(28,28) + return image + + +def get_random_image_from_dataset(x_test, y_test): + image_index=randint(0, 9999) + print('target class (from random test sample): %d' % y_test[image_index]) + return x_test[image_index] + + +def show_selected_image(image): + import matplotlib.pyplot as plt + fig = plt.figure() + plt.subplot(1, 1, 1) + plt.tight_layout() + plt.imshow(image, cmap='gray', interpolation='none') + plt.xticks([]) + plt.yticks([]) + plt.show() + + +def make_vector(image): + vector = [] + for item in image.tolist(): + vector.extend(item) + return vector + + +def make_prediction_request(image, prediction_url, auth, verify): + vector = make_vector(image) + json = { + "inputs": [vector] + } + response = requests.post(prediction_url, json=json, auth=auth, verify=verify) + + print('HTTP Response %s' % response.status_code) + print(response.text) + + +def main(): + parser = argparse.ArgumentParser(description='Test MNIST Tensorflow Server') + parser.add_argument('-u', '--url', help='Prediction HOST URL', default='http://127.0.0.1:8501/v1/models/mnist:predict') + parser.add_argument('-p', '--path', help='Example image path') + parser.add_argument('-U', '--username', help='Basic Auth username') + parser.add_argument('-P', '--password', help='Basic Auth password') + parser.add_argument('-i', '--iterations', type=int, help='Number of iterations; use -1 for forever') + parser.add_argument('-V', '--verify', type=str2bool, help='Verify host SSL/TLS certificates; defaults to True', default=True) + parser.add_argument('-S', '--show', type=str2bool, help='Show sample digit using mathplotlib; defaults to False', default=False) + args = parser.parse_args() + + plotting = False + if args.show: + plotting = try_importing_mathplotlib() + + i = 1 + req_cnt = 0 + if args.iterations: + i = args.iterations + ploting = False + auth = None + if args.username: + if args.password is None: + args.password = getpass.getpass() + auth = (args.username, args.password) + + # Load image from drive if specified, if not load example image from mnist dataset + if args.path: + image = get_image_from_drive(args.path) + else: + (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() + image = get_random_image_from_dataset(x_test, y_test) + + if plotting: + show_selected_image(image) + + while i != 0: + if args.iterations: + req_cnt += 1 + print('Iteration: %d' % req_cnt) + make_prediction_request(image, args.url, auth, args.verify) + if i > 0: + i -= 1 + if i != 0 and args.path is None: + image = get_random_image_from_dataset(x_test, y_test) + + +if __name__ == '__main__': + main() diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/accelerator/__init__.py b/utils/accelerator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/accelerator/tpu.py b/utils/accelerator/tpu.py new file mode 100644 index 0000000..06f55e2 --- /dev/null +++ b/utils/accelerator/tpu.py @@ -0,0 +1,116 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Functions specific to running TensorFlow on TPUs.""" + +import tensorflow as tf + + +# "local" is a magic word in the TPU cluster resolver; it informs the resolver +# to use the local CPU as the compute device. This is useful for testing and +# debugging; the code flow is ostensibly identical, but without the need to +# actually have a TPU on the other end. +LOCAL = "local" + + +def construct_scalar_host_call(metric_dict, model_dir, prefix=""): + """Construct a host call to log scalars when training on TPU. + + Args: + metric_dict: A dict of the tensors to be logged. + model_dir: The location to write the summary. + prefix: The prefix (if any) to prepend to the metric names. + + Returns: + A tuple of (function, args_to_be_passed_to_said_function) + """ + # type: (dict, str) -> (function, list) + metric_names = list(metric_dict.keys()) + + def host_call_fn(global_step, *args): + """Training host call. Creates scalar summaries for training metrics. + + This function is executed on the CPU and should not directly reference + any Tensors in the rest of the `model_fn`. To pass Tensors from the + model to the `metric_fn`, provide as part of the `host_call`. See + https://www.tensorflow.org/api_docs/python/tf/contrib/tpu/TPUEstimatorSpec + for more information. + + Arguments should match the list of `Tensor` objects passed as the second + element in the tuple passed to `host_call`. + + Args: + global_step: `Tensor with shape `[batch]` for the global_step + *args: Remaining tensors to log. + + Returns: + List of summary ops to run on the CPU host. + """ + step = global_step[0] + with tf.contrib.summary.create_file_writer( + logdir=model_dir, filename_suffix=".host_call").as_default(): + with tf.contrib.summary.always_record_summaries(): + for i, name in enumerate(metric_names): + tf.contrib.summary.scalar(prefix + name, args[i][0], step=step) + + return tf.contrib.summary.all_summary_ops() + + # To log the current learning rate, and gradient norm for Tensorboard, the + # summary op needs to be run on the host CPU via host_call. host_call + # expects [batch_size, ...] Tensors, thus reshape to introduce a batch + # dimension. These Tensors are implicitly concatenated to + # [params['batch_size']]. + global_step_tensor = tf.reshape( + tf.compat.v1.train.get_or_create_global_step(), [1]) + other_tensors = [tf.reshape(metric_dict[key], [1]) for key in metric_names] + + return host_call_fn, [global_step_tensor] + other_tensors + + +def embedding_matmul(embedding_table, values, mask, name="embedding_matmul"): + """Performs embedding lookup via a matmul. + + The matrix to be multiplied by the embedding table Tensor is constructed + via an implementation of scatter based on broadcasting embedding indices + and performing an equality comparison against a broadcasted + range(num_embedding_table_rows). All masked positions will produce an + embedding vector of zeros. + + Args: + embedding_table: Tensor of embedding table. + Rank 2 (table_size x embedding dim) + values: Tensor of embedding indices. Rank 2 (batch x n_indices) + mask: Tensor of mask / weights. Rank 2 (batch x n_indices) + name: Optional name scope for created ops + + Returns: + Rank 3 tensor of embedding vectors. + """ + + with tf.name_scope(name): + n_embeddings = embedding_table.get_shape().as_list()[0] + batch_size, padded_size = values.shape.as_list() + + emb_idcs = tf.tile( + tf.reshape(values, (batch_size, padded_size, 1)), (1, 1, n_embeddings)) + emb_weights = tf.tile( + tf.reshape(mask, (batch_size, padded_size, 1)), (1, 1, n_embeddings)) + col_idcs = tf.tile( + tf.reshape(tf.range(n_embeddings), (1, 1, n_embeddings)), + (batch_size, padded_size, 1)) + one_hot = tf.where( + tf.equal(emb_idcs, col_idcs), emb_weights, + tf.zeros((batch_size, padded_size, n_embeddings))) + + return tf.tensordot(one_hot, embedding_table, 1) diff --git a/utils/accelerator/tpu_test.py b/utils/accelerator/tpu_test.py new file mode 100644 index 0000000..450f74e --- /dev/null +++ b/utils/accelerator/tpu_test.py @@ -0,0 +1,108 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Test TPU optimized matmul embedding.""" + +import numpy as np +import tensorflow as tf + +from utils.accelerator import tpu as tpu_utils + + +TEST_CASES = [ + dict(embedding_dim=256, vocab_size=1000, sequence_length=64, + batch_size=32, seed=54131), + dict(embedding_dim=8, vocab_size=15, sequence_length=12, + batch_size=256, seed=536413), + dict(embedding_dim=2048, vocab_size=512, sequence_length=50, + batch_size=8, seed=35124) +] + + +class TPUBaseTester(tf.test.TestCase): + def construct_embedding_and_values(self, embedding_dim, vocab_size, + sequence_length, batch_size, seed): + np.random.seed(seed) + + embeddings = np.random.random(size=(vocab_size, embedding_dim)) + embedding_table = tf.convert_to_tensor(value=embeddings, dtype=tf.float32) + + tokens = np.random.randint(low=1, high=vocab_size-1, + size=(batch_size, sequence_length)) + for i in range(batch_size): + tokens[i, np.random.randint(low=0, high=sequence_length-1):] = 0 + values = tf.convert_to_tensor(value=tokens, dtype=tf.int32) + mask = tf.cast(tf.not_equal(values, 0), dtype=tf.float32) + return embedding_table, values, mask + + def _test_embedding(self, embedding_dim, vocab_size, + sequence_length, batch_size, seed): + """Test that matmul embedding matches embedding lookup (gather).""" + + with self.test_session(): + embedding_table, values, mask = self.construct_embedding_and_values( + embedding_dim=embedding_dim, + vocab_size=vocab_size, + sequence_length=sequence_length, + batch_size=batch_size, + seed=seed + ) + + embedding = (tf.nn.embedding_lookup(params=embedding_table, ids=values) * + tf.expand_dims(mask, -1)) + + matmul_embedding = tpu_utils.embedding_matmul( + embedding_table=embedding_table, values=values, mask=mask) + + self.assertAllClose(embedding, matmul_embedding) + + def _test_masking(self, embedding_dim, vocab_size, + sequence_length, batch_size, seed): + """Test that matmul embedding properly zeros masked positions.""" + with self.test_session(): + embedding_table, values, mask = self.construct_embedding_and_values( + embedding_dim=embedding_dim, + vocab_size=vocab_size, + sequence_length=sequence_length, + batch_size=batch_size, + seed=seed + ) + + matmul_embedding = tpu_utils.embedding_matmul( + embedding_table=embedding_table, values=values, mask=mask) + + self.assertAllClose(matmul_embedding, + matmul_embedding * tf.expand_dims(mask, -1)) + + def test_embedding_0(self): + self._test_embedding(**TEST_CASES[0]) + + def test_embedding_1(self): + self._test_embedding(**TEST_CASES[1]) + + def test_embedding_2(self): + self._test_embedding(**TEST_CASES[2]) + + def test_masking_0(self): + self._test_masking(**TEST_CASES[0]) + + def test_masking_1(self): + self._test_masking(**TEST_CASES[1]) + + def test_masking_2(self): + self._test_masking(**TEST_CASES[2]) + + +if __name__ == "__main__": + tf.test.main() diff --git a/utils/export/__init__.py b/utils/export/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/export/export.py b/utils/export/export.py new file mode 100644 index 0000000..8061c28 --- /dev/null +++ b/utils/export/export.py @@ -0,0 +1,49 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Convenience functions for exporting models as SavedModels or other types.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + + +def build_tensor_serving_input_receiver_fn(shape, dtype=tf.float32, + batch_size=1): + """Returns a input_receiver_fn that can be used during serving. + + This expects examples to come through as float tensors, and simply + wraps them as TensorServingInputReceivers. + + Arguably, this should live in tf.estimator.export. Testing here first. + + Args: + shape: list representing target size of a single example. + dtype: the expected datatype for the input example + batch_size: number of input tensors that will be passed for prediction + + Returns: + A function that itself returns a TensorServingInputReceiver. + """ + def serving_input_receiver_fn(): + # Prep a placeholder where the input example will be fed in + features = tf.compat.v1.placeholder( + dtype=dtype, shape=[batch_size] + shape, name='input_tensor') + + return tf.estimator.export.TensorServingInputReceiver( + features=features, receiver_tensors=features) + + return serving_input_receiver_fn diff --git a/utils/export/export_test.py b/utils/export/export_test.py new file mode 100644 index 0000000..0cd80a0 --- /dev/null +++ b/utils/export/export_test.py @@ -0,0 +1,63 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for exporting utils.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf # pylint: disable=g-bad-import-order + +from utils.export import export + + +class ExportUtilsTest(tf.test.TestCase): + """Tests for the ExportUtils.""" + + def test_build_tensor_serving_input_receiver_fn(self): + receiver_fn = export.build_tensor_serving_input_receiver_fn(shape=[4, 5]) + with tf.Graph().as_default(): + receiver = receiver_fn() + self.assertIsInstance( + receiver, tf.estimator.export.TensorServingInputReceiver) + + self.assertIsInstance(receiver.features, tf.Tensor) + self.assertEqual(receiver.features.shape, tf.TensorShape([1, 4, 5])) + self.assertEqual(receiver.features.dtype, tf.float32) + self.assertIsInstance(receiver.receiver_tensors, dict) + # Note that Python 3 can no longer index .values() directly; cast to list. + self.assertEqual(list(receiver.receiver_tensors.values())[0].shape, + tf.TensorShape([1, 4, 5])) + + def test_build_tensor_serving_input_receiver_fn_batch_dtype(self): + receiver_fn = export.build_tensor_serving_input_receiver_fn( + shape=[4, 5], dtype=tf.int8, batch_size=10) + + with tf.Graph().as_default(): + receiver = receiver_fn() + self.assertIsInstance( + receiver, tf.estimator.export.TensorServingInputReceiver) + + self.assertIsInstance(receiver.features, tf.Tensor) + self.assertEqual(receiver.features.shape, tf.TensorShape([10, 4, 5])) + self.assertEqual(receiver.features.dtype, tf.int8) + self.assertIsInstance(receiver.receiver_tensors, dict) + # Note that Python 3 can no longer index .values() directly; cast to list. + self.assertEqual(list(receiver.receiver_tensors.values())[0].shape, + tf.TensorShape([10, 4, 5])) + + +if __name__ == "__main__": + tf.test.main() diff --git a/utils/flags/README.md b/utils/flags/README.md new file mode 100644 index 0000000..18160f7 --- /dev/null +++ b/utils/flags/README.md @@ -0,0 +1,97 @@ +# Adding Abseil (absl) flags quickstart +## Defining a flag +absl flag definitions are similar to argparse, although they are defined on a global namespace. + +For instance defining a string flag looks like: +```$xslt +from absl import flags +flags.DEFINE_string( + name="my_flag", + default="a_sensible_default", + help="Here is what this flag does." +) +``` + +All three arguments are required, but default may be `None`. A common optional argument is +short_name for defining abreviations. Certain `DEFINE_*` methods will have other required arguments. +For instance `DEFINE_enum` requires the `enum_values` argument to be specified. + +## Key Flags +absl has the concept of a key flag. Any flag defined in `__main__` is considered a key flag by +default. Key flags are displayed in `--help`, others only appear in `--helpfull`. In order to +handle key flags that are defined outside the module in question, absl provides the +`flags.adopt_module_key_flags()` method. This adds the key flags of a different module to one's own +key flags. For example: +```$xslt +File: flag_source.py +--------------------------------------- + +from absl import flags +flags.DEFINE_string(name="my_flag", default="abc", help="a flag.") +``` + +```$xslt +File: my_module.py +--------------------------------------- + +from absl import app as absl_app +from absl import flags + +import flag_source + +flags.adopt_module_key_flags(flag_source) + +def main(_): + pass + +absl_app.run(main, [__file__, "-h"] +``` + +when `my_module.py` is run it will show the help text for `my_flag`. Because not all flags defined +in a file are equally important, `official/utils/flags/core.py` (generally imported as flags_core) +provides an abstraction for handling key flag declaration in an easy way through the +`register_key_flags_in_core()` function, which allows a module to make a single +`adopt_key_flags(flags_core)` call when using the util flag declaration functions. + +## Validators +Often the constraints on a flag are complicated. absl provides the validator decorator to allow +one to mark a function as a flag validation function. Suppose we want users to provide a flag +which is a palindrome. + +```$xslt +from absl import flags + +flags.DEFINE_string(name="pal_flag", short_name="pf", default="", help="Give me a palindrome") + +@flags.validator("pal_flag") +def _check_pal(provided_pal_flag): + return provided_pal_flag == provided_pal_flag[::-1] + +``` + +Validators take the form that returning True (truthy) passes, and all others +(False, None, exception) fail. + +## Testing +To test using absl, simply declare flags in the setupClass method of TensorFlow's TestCase. + +```$xslt +from absl import flags +import tensorflow as tf + +def define_flags(): + flags.DEFINE_string(name="test_flag", default="abc", help="an example flag") + + +class BaseTester(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super(BaseTester, cls).setUpClass() + define_flags() + + def test_trivial(self): + flags_core.parse_flags([__file__, "test_flag", "def"]) + self.AssertEqual(flags.FLAGS.test_flag, "def") + +``` diff --git a/utils/flags/__init__.py b/utils/flags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/flags/_base.py b/utils/flags/_base.py new file mode 100644 index 0000000..4715cbd --- /dev/null +++ b/utils/flags/_base.py @@ -0,0 +1,156 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Flags which will be nearly universal across models.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl import flags +import tensorflow as tf + +from utils.flags._conventions import help_wrap +from utils.logs import hooks_helper + + +def define_base(data_dir=True, model_dir=True, clean=True, train_epochs=True, + epochs_between_evals=True, stop_threshold=True, batch_size=True, + num_gpu=True, hooks=True, export_dir=True, + distribution_strategy=True): + """Register base flags. + + Args: + data_dir: Create a flag for specifying the input data directory. + model_dir: Create a flag for specifying the model file directory. + train_epochs: Create a flag to specify the number of training epochs. + epochs_between_evals: Create a flag to specify the frequency of testing. + stop_threshold: Create a flag to specify a threshold accuracy or other + eval metric which should trigger the end of training. + batch_size: Create a flag to specify the batch size. + num_gpu: Create a flag to specify the number of GPUs used. + hooks: Create a flag to specify hooks for logging. + export_dir: Create a flag to specify where a SavedModel should be exported. + distribution_strategy: Create a flag to specify which Distribution Strategy + to use. + Returns: + A list of flags for core.py to marks as key flags. + """ + key_flags = [] + + if data_dir: + flags.DEFINE_string( + name="data_dir", short_name="dd", default="/tmp", + help=help_wrap("The location of the input data.")) + key_flags.append("data_dir") + + if model_dir: + flags.DEFINE_string( + name="model_dir", short_name="md", default="/tmp", + help=help_wrap("The location of the model checkpoint files.")) + key_flags.append("model_dir") + + if clean: + flags.DEFINE_boolean( + name="clean", default=False, + help=help_wrap("If set, model_dir will be removed if it exists.")) + key_flags.append("clean") + + if train_epochs: + flags.DEFINE_integer( + name="train_epochs", short_name="te", default=1, + help=help_wrap("The number of epochs used to train.")) + key_flags.append("train_epochs") + + if epochs_between_evals: + flags.DEFINE_integer( + name="epochs_between_evals", short_name="ebe", default=1, + help=help_wrap("The number of training epochs to run between " + "evaluations.")) + key_flags.append("epochs_between_evals") + + if stop_threshold: + flags.DEFINE_float( + name="stop_threshold", short_name="st", + default=None, + help=help_wrap("If passed, training will stop at the earlier of " + "train_epochs and when the evaluation metric is " + "greater than or equal to stop_threshold.")) + + if batch_size: + flags.DEFINE_integer( + name="batch_size", short_name="bs", default=32, + help=help_wrap("Batch size for training and evaluation. When using " + "multiple gpus, this is the global batch size for " + "all devices. For example, if the batch size is 32 " + "and there are 4 GPUs, each GPU will get 8 examples on " + "each step.")) + key_flags.append("batch_size") + + if num_gpu: + flags.DEFINE_integer( + name="num_gpus", short_name="ng", + default=1 if tf.test.is_gpu_available() else 0, + help=help_wrap( + "How many GPUs to use at each worker with the " + "DistributionStrategies API. The default is 1 if TensorFlow can " + "detect a GPU, and 0 otherwise.")) + + if hooks: + # Construct a pretty summary of hooks. + hook_list_str = ( + u"\ufeff Hook:\n" + u"\n".join([u"\ufeff {}".format(key) for key + in hooks_helper.HOOKS])) + flags.DEFINE_list( + name="hooks", short_name="hk", default="LoggingTensorHook", + help=help_wrap( + u"A list of (case insensitive) strings to specify the names of " + u"training hooks.\n{}\n\ufeff Example: `--hooks ProfilerHook," + u"ExamplesPerSecondHook`\n See utils.logs.hooks_helper " + u"for details.".format(hook_list_str)) + ) + key_flags.append("hooks") + + if export_dir: + flags.DEFINE_string( + name="export_dir", short_name="ed", default=None, + help=help_wrap("If set, a SavedModel serialization of the model will " + "be exported to this directory at the end of training. " + "See the README for more details and relevant links.") + ) + key_flags.append("export_dir") + + if distribution_strategy: + flags.DEFINE_string( + name="distribution_strategy", short_name="ds", default="default", + help=help_wrap("The Distribution Strategy to use for training. " + "Accepted values are 'off', 'default', 'one_device', " + "'mirrored', 'parameter_server', 'collective', " + "case insensitive. 'off' means not to use " + "Distribution Strategy; 'default' means to choose " + "from `MirroredStrategy` or `OneDeviceStrategy` " + "according to the number of GPUs.") + ) + + return key_flags + + +def get_num_gpus(flags_obj): + """Treat num_gpus=-1 as 'use all'.""" + if flags_obj.num_gpus != -1: + return flags_obj.num_gpus + + from tensorflow.python.client import device_lib # pylint: disable=g-import-not-at-top + local_device_protos = device_lib.list_local_devices() + return sum([1 for d in local_device_protos if d.device_type == "GPU"]) diff --git a/utils/flags/_benchmark.py b/utils/flags/_benchmark.py new file mode 100644 index 0000000..29ebe0e --- /dev/null +++ b/utils/flags/_benchmark.py @@ -0,0 +1,99 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Flags for benchmarking models.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl import flags + +from utils.flags._conventions import help_wrap + + +def define_benchmark(benchmark_log_dir=True, bigquery_uploader=True): + """Register benchmarking flags. + + Args: + benchmark_log_dir: Create a flag to specify location for benchmark logging. + bigquery_uploader: Create flags for uploading results to BigQuery. + + Returns: + A list of flags for core.py to marks as key flags. + """ + + key_flags = [] + + flags.DEFINE_enum( + name="benchmark_logger_type", default="BaseBenchmarkLogger", + enum_values=["BaseBenchmarkLogger", "BenchmarkFileLogger", + "BenchmarkBigQueryLogger"], + help=help_wrap("The type of benchmark logger to use. Defaults to using " + "BaseBenchmarkLogger which logs to STDOUT. Different " + "loggers will require other flags to be able to work.")) + flags.DEFINE_string( + name="benchmark_test_id", short_name="bti", default=None, + help=help_wrap("The unique test ID of the benchmark run. It could be the " + "combination of key parameters. It is hardware " + "independent and could be used compare the performance " + "between different test runs. This flag is designed for " + "human consumption, and does not have any impact within " + "the system.")) + + if benchmark_log_dir: + flags.DEFINE_string( + name="benchmark_log_dir", short_name="bld", default=None, + help=help_wrap("The location of the benchmark logging.") + ) + + if bigquery_uploader: + flags.DEFINE_string( + name="gcp_project", short_name="gp", default=None, + help=help_wrap( + "The GCP project name where the benchmark will be uploaded.")) + + flags.DEFINE_string( + name="bigquery_data_set", short_name="bds", default="test_benchmark", + help=help_wrap( + "The Bigquery dataset name where the benchmark will be uploaded.")) + + flags.DEFINE_string( + name="bigquery_run_table", short_name="brt", default="benchmark_run", + help=help_wrap("The Bigquery table name where the benchmark run " + "information will be uploaded.")) + + flags.DEFINE_string( + name="bigquery_run_status_table", short_name="brst", + default="benchmark_run_status", + help=help_wrap("The Bigquery table name where the benchmark run " + "status information will be uploaded.")) + + flags.DEFINE_string( + name="bigquery_metric_table", short_name="bmt", + default="benchmark_metric", + help=help_wrap("The Bigquery table name where the benchmark metric " + "information will be uploaded.")) + + @flags.multi_flags_validator( + ["benchmark_logger_type", "benchmark_log_dir"], + message="--benchmark_logger_type=BenchmarkFileLogger will require " + "--benchmark_log_dir being set") + def _check_benchmark_log_dir(flags_dict): + benchmark_logger_type = flags_dict["benchmark_logger_type"] + if benchmark_logger_type == "BenchmarkFileLogger": + return flags_dict["benchmark_log_dir"] + return True + + return key_flags diff --git a/utils/flags/_conventions.py b/utils/flags/_conventions.py new file mode 100644 index 0000000..c486d38 --- /dev/null +++ b/utils/flags/_conventions.py @@ -0,0 +1,46 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Central location for shared arparse convention definitions.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import codecs +import functools + +from absl import app as absl_app +from absl import flags + + +# This codifies help string conventions and makes it easy to update them if +# necessary. Currently the only major effect is that help bodies start on the +# line after flags are listed. All flag definitions should wrap the text bodies +# with help wrap when calling DEFINE_*. +_help_wrap = functools.partial(flags.text_wrap, length=80, indent="", + firstline_indent="\n") + + +# Pretty formatting causes issues when utf-8 is not installed on a system. +try: + codecs.lookup("utf-8") + help_wrap = _help_wrap +except LookupError: + def help_wrap(text, *args, **kwargs): + return _help_wrap(text, *args, **kwargs).replace("\ufeff", "") + + +# Replace None with h to also allow -h +absl_app.HelpshortFlag.SHORT_NAME = "h" diff --git a/utils/flags/_device.py b/utils/flags/_device.py new file mode 100644 index 0000000..12aa7fc --- /dev/null +++ b/utils/flags/_device.py @@ -0,0 +1,85 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Flags for managing compute devices. Currently only contains TPU flags.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl import flags +import tensorflow as tf + +from utils.flags._conventions import help_wrap + + +def require_cloud_storage(flag_names): + """Register a validator to check directory flags. + Args: + flag_names: An iterable of strings containing the names of flags to be + checked. + """ + msg = "TPU requires GCS path for {}".format(", ".join(flag_names)) + @flags.multi_flags_validator(["tpu"] + flag_names, message=msg) + def _path_check(flag_values): # pylint: disable=missing-docstring + if flag_values["tpu"] is None: + return True + + valid_flags = True + for key in flag_names: + if not flag_values[key].startswith("gs://"): + tf.compat.v1.logging.error("{} must be a GCS path.".format(key)) + valid_flags = False + + return valid_flags + + +def define_device(tpu=True): + """Register device specific flags. + Args: + tpu: Create flags to specify TPU operation. + Returns: + A list of flags for core.py to marks as key flags. + """ + + key_flags = [] + + if tpu: + flags.DEFINE_string( + name="tpu", default=None, + help=help_wrap( + "The Cloud TPU to use for training. This should be either the name " + "used when creating the Cloud TPU, or a " + "grpc://ip.address.of.tpu:8470 url. Passing `local` will use the" + "CPU of the local instance instead. (Good for debugging.)")) + key_flags.append("tpu") + + flags.DEFINE_string( + name="tpu_zone", default=None, + help=help_wrap( + "[Optional] GCE zone where the Cloud TPU is located in. If not " + "specified, we will attempt to automatically detect the GCE " + "project from metadata.")) + + flags.DEFINE_string( + name="tpu_gcp_project", default=None, + help=help_wrap( + "[Optional] Project name for the Cloud TPU-enabled project. If not " + "specified, we will attempt to automatically detect the GCE " + "project from metadata.")) + + flags.DEFINE_integer(name="num_tpu_shards", default=8, + help=help_wrap("Number of shards (TPU chips).")) + + return key_flags diff --git a/utils/flags/_misc.py b/utils/flags/_misc.py new file mode 100644 index 0000000..cd40b93 --- /dev/null +++ b/utils/flags/_misc.py @@ -0,0 +1,50 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Misc flags.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from absl import flags + +from utils.flags._conventions import help_wrap + + +def define_image(data_format=True): + """Register image specific flags. + + Args: + data_format: Create a flag to specify image axis convention. + + Returns: + A list of flags for core.py to marks as key flags. + """ + + key_flags = [] + + if data_format: + flags.DEFINE_enum( + name="data_format", short_name="df", default=None, + enum_values=["channels_first", "channels_last"], + help=help_wrap( + "A flag to override the data format used in the model. " + "channels_first provides a performance boost on GPU but is not " + "always compatible with CPU. If left unspecified, the data format " + "will be chosen automatically based on whether TensorFlow was " + "built for CPU or GPU.")) + key_flags.append("data_format") + + return key_flags diff --git a/utils/flags/_performance.py b/utils/flags/_performance.py new file mode 100644 index 0000000..f7c95f8 --- /dev/null +++ b/utils/flags/_performance.py @@ -0,0 +1,184 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Register flags for optimizing performance.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import multiprocessing + +from absl import flags # pylint: disable=g-bad-import-order +import tensorflow as tf # pylint: disable=g-bad-import-order + +from utils.flags._conventions import help_wrap + + +# Map string to (TensorFlow dtype, default loss scale) +DTYPE_MAP = { + "fp16": (tf.float16, 128), + "fp32": (tf.float32, 1), +} + + +def get_tf_dtype(flags_obj): + return DTYPE_MAP[flags_obj.dtype][0] + + +def get_loss_scale(flags_obj): + if flags_obj.loss_scale is not None: + return flags_obj.loss_scale + return DTYPE_MAP[flags_obj.dtype][1] + + +def define_performance(num_parallel_calls=True, inter_op=True, intra_op=True, + synthetic_data=True, max_train_steps=True, dtype=True, + all_reduce_alg=True, tf_gpu_thread_mode=False, + datasets_num_private_threads=False, + datasets_num_parallel_batches=False): + """Register flags for specifying performance tuning arguments. + + Args: + num_parallel_calls: Create a flag to specify parallelism of data loading. + inter_op: Create a flag to allow specification of inter op threads. + intra_op: Create a flag to allow specification of intra op threads. + synthetic_data: Create a flag to allow the use of synthetic data. + max_train_steps: Create a flags to allow specification of maximum number + of training steps + dtype: Create flags for specifying dtype. + all_reduce_alg: If set forces a specific algorithm for multi-gpu. + tf_gpu_thread_mode: gpu_private triggers us of private thread pool. + datasets_num_private_threads: Number of private threads for datasets. + datasets_num_parallel_batches: Determines how many batches to process in + parallel when using map and batch from tf.data. + + Returns: + A list of flags for core.py to marks as key flags. + """ + + key_flags = [] + if num_parallel_calls: + flags.DEFINE_integer( + name="num_parallel_calls", short_name="npc", + default=multiprocessing.cpu_count(), + help=help_wrap("The number of records that are processed in parallel " + "during input processing. This can be optimized per " + "data set but for generally homogeneous data sets, " + "should be approximately the number of available CPU " + "cores. (default behavior)")) + + if inter_op: + flags.DEFINE_integer( + name="inter_op_parallelism_threads", short_name="inter", default=0, + help=help_wrap("Number of inter_op_parallelism_threads to use for CPU. " + "See TensorFlow config.proto for details.") + ) + + if intra_op: + flags.DEFINE_integer( + name="intra_op_parallelism_threads", short_name="intra", default=0, + help=help_wrap("Number of intra_op_parallelism_threads to use for CPU. " + "See TensorFlow config.proto for details.")) + + if synthetic_data: + flags.DEFINE_bool( + name="use_synthetic_data", short_name="synth", default=False, + help=help_wrap( + "If set, use fake data (zeroes) instead of a real dataset. " + "This mode is useful for performance debugging, as it removes " + "input processing steps, but will not learn anything.")) + + if max_train_steps: + flags.DEFINE_integer( + name="max_train_steps", short_name="mts", default=None, help=help_wrap( + "The model will stop training if the global_step reaches this " + "value. If not set, training will run until the specified number " + "of epochs have run as usual. It is generally recommended to set " + "--train_epochs=1 when using this flag." + )) + + if dtype: + flags.DEFINE_enum( + name="dtype", short_name="dt", default="fp32", + enum_values=DTYPE_MAP.keys(), + help=help_wrap("The TensorFlow datatype used for calculations. " + "Variables may be cast to a higher precision on a " + "case-by-case basis for numerical stability.")) + + flags.DEFINE_integer( + name="loss_scale", short_name="ls", default=None, + help=help_wrap( + "The amount to scale the loss by when the model is run. Before " + "gradients are computed, the loss is multiplied by the loss scale, " + "making all gradients loss_scale times larger. To adjust for this, " + "gradients are divided by the loss scale before being applied to " + "variables. This is mathematically equivalent to training without " + "a loss scale, but the loss scale helps avoid some intermediate " + "gradients from underflowing to zero. If not provided the default " + "for fp16 is 128 and 1 for all other dtypes.")) + + loss_scale_val_msg = "loss_scale should be a positive integer." + @flags.validator(flag_name="loss_scale", message=loss_scale_val_msg) + def _check_loss_scale(loss_scale): # pylint: disable=unused-variable + if loss_scale is None: + return True # null case is handled in get_loss_scale() + + return loss_scale > 0 + + if all_reduce_alg: + flags.DEFINE_string( + name="all_reduce_alg", short_name="ara", default=None, + help=help_wrap("Defines the algorithm to use for performing all-reduce." + "When specified with MirroredStrategy for single " + "worker, this controls " + "tf.contrib.distribute.AllReduceCrossTowerOps. When " + "specified with MultiWorkerMirroredStrategy, this " + "controls " + "tf.distribute.experimental.CollectiveCommunication; " + "valid options are `ring` and `nccl`.")) + + if tf_gpu_thread_mode: + flags.DEFINE_string( + name="tf_gpu_thread_mode", short_name="gt_mode", default=None, + help=help_wrap( + "Whether and how the GPU device uses its own threadpool.") + ) + + flags.DEFINE_integer( + name="per_gpu_thread_count", short_name="pgtc", default=0, + help=help_wrap( + "The number of threads to use for GPU. Only valid when " + "tf_gpu_thread_mode is not global.") + ) + + if datasets_num_private_threads: + flags.DEFINE_integer( + name="datasets_num_private_threads", + default=None, + help=help_wrap( + "Number of threads for a private threadpool created for all" + "datasets computation..") + ) + + if datasets_num_parallel_batches: + flags.DEFINE_integer( + name="datasets_num_parallel_batches", + default=None, + help=help_wrap( + "Determines how many batches to process in parallel when using " + "map and batch from tf.data.") + ) + + return key_flags diff --git a/utils/flags/core.py b/utils/flags/core.py new file mode 100644 index 0000000..5fa01e7 --- /dev/null +++ b/utils/flags/core.py @@ -0,0 +1,88 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Public interface for flag definition. + +See _example.py for detailed instructions on defining flags. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import functools +import sys + +from absl import app as absl_app +from absl import flags + +from utils.flags import _base +from utils.flags import _benchmark +from utils.flags import _conventions +from utils.flags import _device +from utils.flags import _misc +from utils.flags import _performance + + +def set_defaults(**kwargs): + for key, value in kwargs.items(): + flags.FLAGS.set_default(name=key, value=value) + + +def parse_flags(argv=None): + """Reset flags and reparse. Currently only used in testing.""" + flags.FLAGS.unparse_flags() + absl_app.parse_flags_with_usage(argv or sys.argv) + + +def register_key_flags_in_core(f): + """Defines a function in core.py, and registers its key flags. + + absl uses the location of a flags.declare_key_flag() to determine the context + in which a flag is key. By making all declares in core, this allows model + main functions to call flags.adopt_module_key_flags() on core and correctly + chain key flags. + + Args: + f: The function to be wrapped + + Returns: + The "core-defined" version of the input function. + """ + + def core_fn(*args, **kwargs): + key_flags = f(*args, **kwargs) + [flags.declare_key_flag(fl) for fl in key_flags] # pylint: disable=expression-not-assigned + return core_fn + + +define_base = register_key_flags_in_core(_base.define_base) +# Remove options not relevant for Eager from define_base(). +define_base_eager = register_key_flags_in_core(functools.partial( + _base.define_base, epochs_between_evals=False, stop_threshold=False, + hooks=False)) +define_benchmark = register_key_flags_in_core(_benchmark.define_benchmark) +define_device = register_key_flags_in_core(_device.define_device) +define_image = register_key_flags_in_core(_misc.define_image) +define_performance = register_key_flags_in_core(_performance.define_performance) + + +help_wrap = _conventions.help_wrap + + +get_num_gpus = _base.get_num_gpus +get_tf_dtype = _performance.get_tf_dtype +get_loss_scale = _performance.get_loss_scale +DTYPE_MAP = _performance.DTYPE_MAP +require_cloud_storage = _device.require_cloud_storage diff --git a/utils/flags/flags_test.py b/utils/flags/flags_test.py new file mode 100644 index 0000000..04147a5 --- /dev/null +++ b/utils/flags/flags_test.py @@ -0,0 +1,100 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +import unittest + +from absl import flags +import tensorflow as tf + +from utils.flags import core as flags_core # pylint: disable=g-bad-import-order + + +def define_flags(): + flags_core.define_base(num_gpu=False) + flags_core.define_performance() + flags_core.define_image() + flags_core.define_benchmark() + + +class BaseTester(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super(BaseTester, cls).setUpClass() + define_flags() + + def test_default_setting(self): + """Test to ensure fields exist and defaults can be set. + """ + + defaults = dict( + data_dir="dfgasf", + model_dir="dfsdkjgbs", + train_epochs=534, + epochs_between_evals=15, + batch_size=256, + hooks=["LoggingTensorHook"], + num_parallel_calls=18, + inter_op_parallelism_threads=5, + intra_op_parallelism_threads=10, + data_format="channels_first" + ) + + flags_core.set_defaults(**defaults) + flags_core.parse_flags() + + for key, value in defaults.items(): + assert flags.FLAGS.get_flag_value(name=key, default=None) == value + + def test_benchmark_setting(self): + defaults = dict( + hooks=["LoggingMetricHook"], + benchmark_log_dir="/tmp/12345", + gcp_project="project_abc", + ) + + flags_core.set_defaults(**defaults) + flags_core.parse_flags() + + for key, value in defaults.items(): + assert flags.FLAGS.get_flag_value(name=key, default=None) == value + + def test_booleans(self): + """Test to ensure boolean flags trigger as expected. + """ + + flags_core.parse_flags([__file__, "--use_synthetic_data"]) + + assert flags.FLAGS.use_synthetic_data + + def test_parse_dtype_info(self): + for dtype_str, tf_dtype, loss_scale in [["fp16", tf.float16, 128], + ["fp32", tf.float32, 1]]: + flags_core.parse_flags([__file__, "--dtype", dtype_str]) + + self.assertEqual(flags_core.get_tf_dtype(flags.FLAGS), tf_dtype) + self.assertEqual(flags_core.get_loss_scale(flags.FLAGS), loss_scale) + + flags_core.parse_flags( + [__file__, "--dtype", dtype_str, "--loss_scale", "5"]) + + self.assertEqual(flags_core.get_loss_scale(flags.FLAGS), 5) + + with self.assertRaises(SystemExit): + flags_core.parse_flags([__file__, "--dtype", "int8"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/utils/flags/guidelines.md b/utils/flags/guidelines.md new file mode 100644 index 0000000..f2a6e82 --- /dev/null +++ b/utils/flags/guidelines.md @@ -0,0 +1,64 @@ +# Using flags in official models + +1. **All common flags must be incorporated in the models.** + + Common flags (i.e. batch_size, model_dir, etc.) are provided by various flag definition functions, + and channeled through `utils.flags.core`. For instance to define common supervised + learning parameters one could use the following code: + + ```$xslt + from absl import app as absl_app + from absl import flags + + from utils.flags import core as flags_core + + + def define_flags(): + flags_core.define_base() + flags.adopt_key_flags(flags_core) + + + def main(_): + flags_obj = flags.FLAGS + print(flags_obj) + + + if __name__ == "__main__" + absl_app.run(main) + ``` +2. **Validate flag values.** + + See the [Validators](#validators) section for implementation details. + + Validators in the official model repo should not access the file system, such as verifying + that files exist, due to the strict ordering requirements. + +3. **Flag values should not be mutated.** + + Instead of mutating flag values, use getter functions to return the desired values. An example + getter function is `get_loss_scale` function below: + + ``` + # Map string to (TensorFlow dtype, default loss scale) + DTYPE_MAP = { + "fp16": (tf.float16, 128), + "fp32": (tf.float32, 1), + } + + + def get_loss_scale(flags_obj): + if flags_obj.loss_scale is not None: + return flags_obj.loss_scale + return DTYPE_MAP[flags_obj.dtype][1] + + + def main(_): + flags_obj = flags.FLAGS() + + # Do not mutate flags_obj + # if flags_obj.loss_scale is None: + # flags_obj.loss_scale = DTYPE_MAP[flags_obj.dtype][1] # Don't do this + + print(get_loss_scale(flags_obj)) + ... + ``` \ No newline at end of file diff --git a/utils/logs/__init__.py b/utils/logs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/logs/guidelines.md b/utils/logs/guidelines.md new file mode 100644 index 0000000..7047146 --- /dev/null +++ b/utils/logs/guidelines.md @@ -0,0 +1,58 @@ +# Logging in official models + +This library adds logging functions that print or save tensor values. Official models should define all common hooks +(using hooks helper) and a benchmark logger. + +1. **Training Hooks** + + Hooks are a TensorFlow concept that define specific actions at certain points of the execution. We use them to obtain and log + tensor values during training. + + hooks_helper.py provides an easy way to create common hooks. The following hooks are currently defined: + * LoggingTensorHook: Logs tensor values + * ProfilerHook: Writes a timeline json that can be loaded into chrome://tracing. + * ExamplesPerSecondHook: Logs the number of examples processed per second. + * LoggingMetricHook: Similar to LoggingTensorHook, except that the tensors are logged in a format defined by our data + anaylsis pipeline. + + +2. **Benchmarks** + + The benchmark logger provides useful functions for logging environment information, and evaluation results. + The module also contains a context which is used to update the status of the run. + +Example usage: + +``` +from absl import app as absl_app + +from utils.logs import hooks_helper +from utils.logs import logger + +def model_main(flags_obj): + estimator = ... + + benchmark_logger = logger.get_benchmark_logger() + benchmark_logger.log_run_info(...) + + train_hooks = hooks_helper.get_train_hooks(...) + + for epoch in range(10): + estimator.train(..., hooks=train_hooks) + eval_results = estimator.evaluate(...) + + # Log a dictionary of metrics + benchmark_logger.log_evaluation_result(eval_results) + + # Log an individual metric + benchmark_logger.log_metric(...) + + +def main(_): + with logger.benchmark_context(flags.FLAGS): + model_main(flags.FLAGS) + +if __name__ == "__main__": + # define flags + absl_app.run(main) +``` diff --git a/utils/logs/hooks.py b/utils/logs/hooks.py new file mode 100644 index 0000000..50ace2d --- /dev/null +++ b/utils/logs/hooks.py @@ -0,0 +1,130 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Hook that counts examples per second every N steps or seconds.""" + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf # pylint: disable=g-bad-import-order + +from utils.logs import logger + + +class ExamplesPerSecondHook(tf.estimator.SessionRunHook): + """Hook to print out examples per second. + + Total time is tracked and then divided by the total number of steps + to get the average step time and then batch_size is used to determine + the running average of examples per second. The examples per second for the + most recent interval is also logged. + """ + + def __init__(self, + batch_size, + every_n_steps=None, + every_n_secs=None, + warm_steps=0, + metric_logger=None): + """Initializer for ExamplesPerSecondHook. + + Args: + batch_size: Total batch size across all workers used to calculate + examples/second from global time. + every_n_steps: Log stats every n steps. + every_n_secs: Log stats every n seconds. Exactly one of the + `every_n_steps` or `every_n_secs` should be set. + warm_steps: The number of steps to be skipped before logging and running + average calculation. warm_steps steps refers to global steps across all + workers, not on each worker + metric_logger: instance of `BenchmarkLogger`, the benchmark logger that + hook should use to write the log. If None, BaseBenchmarkLogger will + be used. + + Raises: + ValueError: if neither `every_n_steps` or `every_n_secs` is set, or + both are set. + """ + + if (every_n_steps is None) == (every_n_secs is None): + raise ValueError("exactly one of every_n_steps" + " and every_n_secs should be provided.") + + self._logger = metric_logger or logger.BaseBenchmarkLogger() + + self._timer = tf.estimator.SecondOrStepTimer( + every_steps=every_n_steps, every_secs=every_n_secs) + + self._step_train_time = 0 + self._total_steps = 0 + self._batch_size = batch_size + self._warm_steps = warm_steps + # List of examples per second logged every_n_steps. + self.current_examples_per_sec_list = [] + + def begin(self): + """Called once before using the session to check global step.""" + self._global_step_tensor = tf.compat.v1.train.get_global_step() + if self._global_step_tensor is None: + raise RuntimeError( + "Global step should be created to use StepCounterHook.") + + def before_run(self, run_context): # pylint: disable=unused-argument + """Called before each call to run(). + + Args: + run_context: A SessionRunContext object. + + Returns: + A SessionRunArgs object or None if never triggered. + """ + return tf.estimator.SessionRunArgs(self._global_step_tensor) + + def after_run(self, run_context, run_values): # pylint: disable=unused-argument + """Called after each call to run(). + + Args: + run_context: A SessionRunContext object. + run_values: A SessionRunValues object. + """ + global_step = run_values.results + + if self._timer.should_trigger_for_step( + global_step) and global_step > self._warm_steps: + elapsed_time, elapsed_steps = self._timer.update_last_triggered_step( + global_step) + if elapsed_time is not None: + self._step_train_time += elapsed_time + self._total_steps += elapsed_steps + + # average examples per second is based on the total (accumulative) + # training steps and training time so far + average_examples_per_sec = self._batch_size * ( + self._total_steps / self._step_train_time) + # current examples per second is based on the elapsed training steps + # and training time per batch + current_examples_per_sec = self._batch_size * ( + elapsed_steps / elapsed_time) + # Logs entries to be read from hook during or after run. + self.current_examples_per_sec_list.append(current_examples_per_sec) + self._logger.log_metric( + "average_examples_per_sec", average_examples_per_sec, + global_step=global_step) + + self._logger.log_metric( + "current_examples_per_sec", current_examples_per_sec, + global_step=global_step) diff --git a/utils/logs/hooks_helper.py b/utils/logs/hooks_helper.py new file mode 100644 index 0000000..eb50ec7 --- /dev/null +++ b/utils/logs/hooks_helper.py @@ -0,0 +1,165 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Hooks helper to return a list of TensorFlow hooks for training by name. + +More hooks can be added to this set. To add a new hook, 1) add the new hook to +the registry in HOOKS, 2) add a corresponding function that parses out necessary +parameters. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf # pylint: disable=g-bad-import-order + +from utils.logs import hooks +from utils.logs import logger +from utils.logs import metric_hook + +_TENSORS_TO_LOG = dict((x, x) for x in ['learning_rate', + 'cross_entropy', + 'train_accuracy']) + + +def get_train_hooks(name_list, use_tpu=False, **kwargs): + """Factory for getting a list of TensorFlow hooks for training by name. + + Args: + name_list: a list of strings to name desired hook classes. Allowed: + LoggingTensorHook, ProfilerHook, ExamplesPerSecondHook, which are defined + as keys in HOOKS + use_tpu: Boolean of whether computation occurs on a TPU. This will disable + hooks altogether. + **kwargs: a dictionary of arguments to the hooks. + + Returns: + list of instantiated hooks, ready to be used in a classifier.train call. + + Raises: + ValueError: if an unrecognized name is passed. + """ + + if not name_list: + return [] + + if use_tpu: + tf.compat.v1.logging.warning('hooks_helper received name_list `{}`, but a ' + 'TPU is specified. No hooks will be used.' + .format(name_list)) + return [] + + train_hooks = [] + for name in name_list: + hook_name = HOOKS.get(name.strip().lower()) + if hook_name is None: + raise ValueError('Unrecognized training hook requested: {}'.format(name)) + else: + train_hooks.append(hook_name(**kwargs)) + + return train_hooks + + +def get_logging_tensor_hook(every_n_iter=100, tensors_to_log=None, **kwargs): # pylint: disable=unused-argument + """Function to get LoggingTensorHook. + + Args: + every_n_iter: `int`, print the values of `tensors` once every N local + steps taken on the current worker. + tensors_to_log: List of tensor names or dictionary mapping labels to tensor + names. If not set, log _TENSORS_TO_LOG by default. + **kwargs: a dictionary of arguments to LoggingTensorHook. + + Returns: + Returns a LoggingTensorHook with a standard set of tensors that will be + printed to stdout. + """ + if tensors_to_log is None: + tensors_to_log = _TENSORS_TO_LOG + + return tf.estimator.LoggingTensorHook( + tensors=tensors_to_log, + every_n_iter=every_n_iter) + + +def get_profiler_hook(model_dir, save_steps=1000, **kwargs): # pylint: disable=unused-argument + """Function to get ProfilerHook. + + Args: + model_dir: The directory to save the profile traces to. + save_steps: `int`, print profile traces every N steps. + **kwargs: a dictionary of arguments to ProfilerHook. + + Returns: + Returns a ProfilerHook that writes out timelines that can be loaded into + profiling tools like chrome://tracing. + """ + return tf.estimator.ProfilerHook(save_steps=save_steps, output_dir=model_dir) + + +def get_examples_per_second_hook(every_n_steps=100, + batch_size=128, + warm_steps=5, + **kwargs): # pylint: disable=unused-argument + """Function to get ExamplesPerSecondHook. + + Args: + every_n_steps: `int`, print current and average examples per second every + N steps. + batch_size: `int`, total batch size used to calculate examples/second from + global time. + warm_steps: skip this number of steps before logging and running average. + **kwargs: a dictionary of arguments to ExamplesPerSecondHook. + + Returns: + Returns a ProfilerHook that writes out timelines that can be loaded into + profiling tools like chrome://tracing. + """ + return hooks.ExamplesPerSecondHook( + batch_size=batch_size, every_n_steps=every_n_steps, + warm_steps=warm_steps, metric_logger=logger.get_benchmark_logger()) + + +def get_logging_metric_hook(tensors_to_log=None, + every_n_secs=600, + **kwargs): # pylint: disable=unused-argument + """Function to get LoggingMetricHook. + + Args: + tensors_to_log: List of tensor names or dictionary mapping labels to tensor + names. If not set, log _TENSORS_TO_LOG by default. + every_n_secs: `int`, the frequency for logging the metric. Default to every + 10 mins. + **kwargs: a dictionary of arguments. + + Returns: + Returns a LoggingMetricHook that saves tensor values in a JSON format. + """ + if tensors_to_log is None: + tensors_to_log = _TENSORS_TO_LOG + return metric_hook.LoggingMetricHook( + tensors=tensors_to_log, + metric_logger=logger.get_benchmark_logger(), + every_n_secs=every_n_secs) + + +# A dictionary to map one hook name and its corresponding function +HOOKS = { + 'loggingtensorhook': get_logging_tensor_hook, + 'profilerhook': get_profiler_hook, + 'examplespersecondhook': get_examples_per_second_hook, + 'loggingmetrichook': get_logging_metric_hook, +} diff --git a/utils/logs/hooks_helper_test.py b/utils/logs/hooks_helper_test.py new file mode 100644 index 0000000..167001b --- /dev/null +++ b/utils/logs/hooks_helper_test.py @@ -0,0 +1,67 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Tests for hooks_helper.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest + +import tensorflow as tf # pylint: disable=g-bad-import-order + +from utils.logs import hooks_helper + + +class BaseTest(unittest.TestCase): + + def test_raise_in_non_list_names(self): + with self.assertRaises(ValueError): + hooks_helper.get_train_hooks( + 'LoggingTensorHook, ProfilerHook', model_dir="", batch_size=256) + + def test_raise_in_invalid_names(self): + invalid_names = ['StepCounterHook', 'StopAtStepHook'] + with self.assertRaises(ValueError): + hooks_helper.get_train_hooks(invalid_names, model_dir="", batch_size=256) + + def validate_train_hook_name(self, + test_hook_name, + expected_hook_name, + **kwargs): + returned_hook = hooks_helper.get_train_hooks( + [test_hook_name], model_dir="", **kwargs) + self.assertEqual(len(returned_hook), 1) + self.assertIsInstance(returned_hook[0], tf.estimator.SessionRunHook) + self.assertEqual(returned_hook[0].__class__.__name__.lower(), + expected_hook_name) + + def test_get_train_hooks_logging_tensor_hook(self): + self.validate_train_hook_name('LoggingTensorHook', 'loggingtensorhook') + + def test_get_train_hooks_profiler_hook(self): + self.validate_train_hook_name('ProfilerHook', 'profilerhook') + + def test_get_train_hooks_examples_per_second_hook(self): + self.validate_train_hook_name('ExamplesPerSecondHook', + 'examplespersecondhook') + + def test_get_logging_metric_hook(self): + test_hook_name = 'LoggingMetricHook' + self.validate_train_hook_name(test_hook_name, 'loggingmetrichook') + +if __name__ == '__main__': + tf.test.main() diff --git a/utils/logs/hooks_test.py b/utils/logs/hooks_test.py new file mode 100644 index 0000000..792abf4 --- /dev/null +++ b/utils/logs/hooks_test.py @@ -0,0 +1,158 @@ +# Copyright 2017 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Tests for hooks.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import time + +import tensorflow as tf # pylint: disable=g-bad-import-order + +from utils.logs import hooks +from utils.testing import mock_lib + +tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.DEBUG) + + +class ExamplesPerSecondHookTest(tf.test.TestCase): + """Tests for the ExamplesPerSecondHook. + + In the test, we explicitly run global_step tensor after train_op in order to + keep the global_step value and the train_op (which increase the glboal_step + by 1) consistent. This is to correct the discrepancies in reported global_step + value when running on GPUs. + """ + + def setUp(self): + """Mock out logging calls to verify if correct info is being monitored.""" + self._logger = mock_lib.MockBenchmarkLogger() + + self.graph = tf.Graph() + with self.graph.as_default(): + tf.compat.v1.train.create_global_step() + self.train_op = tf.compat.v1.assign_add( + tf.compat.v1.train.get_global_step(), 1) + self.global_step = tf.compat.v1.train.get_global_step() + + def test_raise_in_both_secs_and_steps(self): + with self.assertRaises(ValueError): + hooks.ExamplesPerSecondHook( + batch_size=256, + every_n_steps=10, + every_n_secs=20, + metric_logger=self._logger) + + def test_raise_in_none_secs_and_steps(self): + with self.assertRaises(ValueError): + hooks.ExamplesPerSecondHook( + batch_size=256, + every_n_steps=None, + every_n_secs=None, + metric_logger=self._logger) + + def _validate_log_every_n_steps(self, every_n_steps, warm_steps): + hook = hooks.ExamplesPerSecondHook( + batch_size=256, + every_n_steps=every_n_steps, + warm_steps=warm_steps, + metric_logger=self._logger) + + with tf.compat.v1.train.MonitoredSession( + tf.compat.v1.train.ChiefSessionCreator(), [hook]) as mon_sess: + for _ in range(every_n_steps): + # Explicitly run global_step after train_op to get the accurate + # global_step value + mon_sess.run(self.train_op) + mon_sess.run(self.global_step) + # Nothing should be in the list yet + self.assertFalse(self._logger.logged_metric) + + mon_sess.run(self.train_op) + global_step_val = mon_sess.run(self.global_step) + + if global_step_val > warm_steps: + self._assert_metrics() + else: + # Nothing should be in the list yet + self.assertFalse(self._logger.logged_metric) + + # Add additional run to verify proper reset when called multiple times. + prev_log_len = len(self._logger.logged_metric) + mon_sess.run(self.train_op) + global_step_val = mon_sess.run(self.global_step) + + if every_n_steps == 1 and global_step_val > warm_steps: + # Each time, we log two additional metrics. Did exactly 2 get added? + self.assertEqual(len(self._logger.logged_metric), prev_log_len + 2) + else: + # No change in the size of the metric list. + self.assertEqual(len(self._logger.logged_metric), prev_log_len) + + def test_examples_per_sec_every_1_steps(self): + with self.graph.as_default(): + self._validate_log_every_n_steps(1, 0) + + def test_examples_per_sec_every_5_steps(self): + with self.graph.as_default(): + self._validate_log_every_n_steps(5, 0) + + def test_examples_per_sec_every_1_steps_with_warm_steps(self): + with self.graph.as_default(): + self._validate_log_every_n_steps(1, 10) + + def test_examples_per_sec_every_5_steps_with_warm_steps(self): + with self.graph.as_default(): + self._validate_log_every_n_steps(5, 10) + + def _validate_log_every_n_secs(self, every_n_secs): + hook = hooks.ExamplesPerSecondHook( + batch_size=256, + every_n_steps=None, + every_n_secs=every_n_secs, + metric_logger=self._logger) + + with tf.compat.v1.train.MonitoredSession( + tf.compat.v1.train.ChiefSessionCreator(), [hook]) as mon_sess: + # Explicitly run global_step after train_op to get the accurate + # global_step value + mon_sess.run(self.train_op) + mon_sess.run(self.global_step) + # Nothing should be in the list yet + self.assertFalse(self._logger.logged_metric) + time.sleep(every_n_secs) + + mon_sess.run(self.train_op) + mon_sess.run(self.global_step) + self._assert_metrics() + + def test_examples_per_sec_every_1_secs(self): + with self.graph.as_default(): + self._validate_log_every_n_secs(1) + + def test_examples_per_sec_every_5_secs(self): + with self.graph.as_default(): + self._validate_log_every_n_secs(5) + + def _assert_metrics(self): + metrics = self._logger.logged_metric + self.assertEqual(metrics[-2]["name"], "average_examples_per_sec") + self.assertEqual(metrics[-1]["name"], "current_examples_per_sec") + + +if __name__ == "__main__": + tf.test.main() diff --git a/utils/logs/logger.py b/utils/logs/logger.py new file mode 100644 index 0000000..e9edab3 --- /dev/null +++ b/utils/logs/logger.py @@ -0,0 +1,442 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Logging utilities for benchmark. + +For collecting local environment metrics like CPU and memory, certain python +packages need be installed. See README for details. +""" +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import contextlib +import datetime +import json +import multiprocessing +import numbers +import os +import threading +import uuid + +from six.moves import _thread as thread +from absl import flags +import tensorflow as tf +from tensorflow.python.client import device_lib + +METRIC_LOG_FILE_NAME = "metric.log" +BENCHMARK_RUN_LOG_FILE_NAME = "benchmark_run.log" +_DATE_TIME_FORMAT_PATTERN = "%Y-%m-%dT%H:%M:%S.%fZ" +GCP_TEST_ENV = "GCP" +RUN_STATUS_SUCCESS = "success" +RUN_STATUS_FAILURE = "failure" +RUN_STATUS_RUNNING = "running" + +FLAGS = flags.FLAGS + +# Don't use it directly. Use get_benchmark_logger to access a logger. +_benchmark_logger = None +_logger_lock = threading.Lock() + + +def config_benchmark_logger(flag_obj=None): + """Config the global benchmark logger.""" + _logger_lock.acquire() + try: + global _benchmark_logger + if not flag_obj: + flag_obj = FLAGS + + if (not hasattr(flag_obj, "benchmark_logger_type") or + flag_obj.benchmark_logger_type == "BaseBenchmarkLogger"): + _benchmark_logger = BaseBenchmarkLogger() + elif flag_obj.benchmark_logger_type == "BenchmarkFileLogger": + _benchmark_logger = BenchmarkFileLogger(flag_obj.benchmark_log_dir) + elif flag_obj.benchmark_logger_type == "BenchmarkBigQueryLogger": + from benchmark import benchmark_uploader as bu # pylint: disable=g-import-not-at-top + bq_uploader = bu.BigQueryUploader(gcp_project=flag_obj.gcp_project) + _benchmark_logger = BenchmarkBigQueryLogger( + bigquery_uploader=bq_uploader, + bigquery_data_set=flag_obj.bigquery_data_set, + bigquery_run_table=flag_obj.bigquery_run_table, + bigquery_run_status_table=flag_obj.bigquery_run_status_table, + bigquery_metric_table=flag_obj.bigquery_metric_table, + run_id=str(uuid.uuid4())) + else: + raise ValueError("Unrecognized benchmark_logger_type: %s" + % flag_obj.benchmark_logger_type) + + finally: + _logger_lock.release() + return _benchmark_logger + + +def get_benchmark_logger(): + if not _benchmark_logger: + config_benchmark_logger() + return _benchmark_logger + + +@contextlib.contextmanager +def benchmark_context(flag_obj): + """Context of benchmark, which will update status of the run accordingly.""" + benchmark_logger = config_benchmark_logger(flag_obj) + try: + yield + benchmark_logger.on_finish(RUN_STATUS_SUCCESS) + except Exception: # pylint: disable=broad-except + # Catch all the exception, update the run status to be failure, and re-raise + benchmark_logger.on_finish(RUN_STATUS_FAILURE) + raise + + +class BaseBenchmarkLogger(object): + """Class to log the benchmark information to STDOUT.""" + + def log_evaluation_result(self, eval_results): + """Log the evaluation result. + + The evaluate result is a dictionary that contains metrics defined in + model_fn. It also contains a entry for global_step which contains the value + of the global step when evaluation was performed. + + Args: + eval_results: dict, the result of evaluate. + """ + if not isinstance(eval_results, dict): + tf.compat.v1.logging.warning( + "eval_results should be dictionary for logging. Got %s", + type(eval_results)) + return + global_step = eval_results[tf.compat.v1.GraphKeys.GLOBAL_STEP] + for key in sorted(eval_results): + if key != tf.compat.v1.GraphKeys.GLOBAL_STEP: + self.log_metric(key, eval_results[key], global_step=global_step) + + def log_metric(self, name, value, unit=None, global_step=None, extras=None): + """Log the benchmark metric information to local file. + + Currently the logging is done in a synchronized way. This should be updated + to log asynchronously. + + Args: + name: string, the name of the metric to log. + value: number, the value of the metric. The value will not be logged if it + is not a number type. + unit: string, the unit of the metric, E.g "image per second". + global_step: int, the global_step when the metric is logged. + extras: map of string:string, the extra information about the metric. + """ + metric = _process_metric_to_json(name, value, unit, global_step, extras) + if metric: + tf.compat.v1.logging.info("Benchmark metric: %s", metric) + + def log_run_info(self, model_name, dataset_name, run_params, test_id=None): + tf.compat.v1.logging.info( + "Benchmark run: %s", _gather_run_info(model_name, dataset_name, + run_params, test_id)) + + def on_finish(self, status): + pass + + +class BenchmarkFileLogger(BaseBenchmarkLogger): + """Class to log the benchmark information to local disk.""" + + def __init__(self, logging_dir): + super(BenchmarkFileLogger, self).__init__() + self._logging_dir = logging_dir + if not tf.io.gfile.isdir(self._logging_dir): + tf.io.gfile.makedirs(self._logging_dir) + self._metric_file_handler = tf.io.gfile.GFile( + os.path.join(self._logging_dir, METRIC_LOG_FILE_NAME), "a") + + def log_metric(self, name, value, unit=None, global_step=None, extras=None): + """Log the benchmark metric information to local file. + + Currently the logging is done in a synchronized way. This should be updated + to log asynchronously. + + Args: + name: string, the name of the metric to log. + value: number, the value of the metric. The value will not be logged if it + is not a number type. + unit: string, the unit of the metric, E.g "image per second". + global_step: int, the global_step when the metric is logged. + extras: map of string:string, the extra information about the metric. + """ + metric = _process_metric_to_json(name, value, unit, global_step, extras) + if metric: + try: + json.dump(metric, self._metric_file_handler) + self._metric_file_handler.write("\n") + self._metric_file_handler.flush() + except (TypeError, ValueError) as e: + tf.compat.v1.logging.warning( + "Failed to dump metric to log file: name %s, value %s, error %s", + name, value, e) + + def log_run_info(self, model_name, dataset_name, run_params, test_id=None): + """Collect most of the TF runtime information for the local env. + + The schema of the run info follows official/benchmark/datastore/schema. + + Args: + model_name: string, the name of the model. + dataset_name: string, the name of dataset for training and evaluation. + run_params: dict, the dictionary of parameters for the run, it could + include hyperparameters or other params that are important for the run. + test_id: string, the unique name of the test run by the combination of key + parameters, eg batch size, num of GPU. It is hardware independent. + """ + run_info = _gather_run_info(model_name, dataset_name, run_params, test_id) + + with tf.io.gfile.GFile(os.path.join( + self._logging_dir, BENCHMARK_RUN_LOG_FILE_NAME), "w") as f: + try: + json.dump(run_info, f) + f.write("\n") + except (TypeError, ValueError) as e: + tf.compat.v1.logging.warning( + "Failed to dump benchmark run info to log file: %s", e) + + def on_finish(self, status): + self._metric_file_handler.flush() + self._metric_file_handler.close() + + +class BenchmarkBigQueryLogger(BaseBenchmarkLogger): + """Class to log the benchmark information to BigQuery data store.""" + + def __init__(self, + bigquery_uploader, + bigquery_data_set, + bigquery_run_table, + bigquery_run_status_table, + bigquery_metric_table, + run_id): + super(BenchmarkBigQueryLogger, self).__init__() + self._bigquery_uploader = bigquery_uploader + self._bigquery_data_set = bigquery_data_set + self._bigquery_run_table = bigquery_run_table + self._bigquery_run_status_table = bigquery_run_status_table + self._bigquery_metric_table = bigquery_metric_table + self._run_id = run_id + + def log_metric(self, name, value, unit=None, global_step=None, extras=None): + """Log the benchmark metric information to bigquery. + + Args: + name: string, the name of the metric to log. + value: number, the value of the metric. The value will not be logged if it + is not a number type. + unit: string, the unit of the metric, E.g "image per second". + global_step: int, the global_step when the metric is logged. + extras: map of string:string, the extra information about the metric. + """ + metric = _process_metric_to_json(name, value, unit, global_step, extras) + if metric: + # Starting new thread for bigquery upload in case it might take long time + # and impact the benchmark and performance measurement. Starting a new + # thread might have potential performance impact for model that run on + # CPU. + thread.start_new_thread( + self._bigquery_uploader.upload_benchmark_metric_json, + (self._bigquery_data_set, + self._bigquery_metric_table, + self._run_id, + [metric])) + + def log_run_info(self, model_name, dataset_name, run_params, test_id=None): + """Collect most of the TF runtime information for the local env. + + The schema of the run info follows official/benchmark/datastore/schema. + + Args: + model_name: string, the name of the model. + dataset_name: string, the name of dataset for training and evaluation. + run_params: dict, the dictionary of parameters for the run, it could + include hyperparameters or other params that are important for the run. + test_id: string, the unique name of the test run by the combination of key + parameters, eg batch size, num of GPU. It is hardware independent. + """ + run_info = _gather_run_info(model_name, dataset_name, run_params, test_id) + # Starting new thread for bigquery upload in case it might take long time + # and impact the benchmark and performance measurement. Starting a new + # thread might have potential performance impact for model that run on CPU. + thread.start_new_thread( + self._bigquery_uploader.upload_benchmark_run_json, + (self._bigquery_data_set, + self._bigquery_run_table, + self._run_id, + run_info)) + thread.start_new_thread( + self._bigquery_uploader.insert_run_status, + (self._bigquery_data_set, + self._bigquery_run_status_table, + self._run_id, + RUN_STATUS_RUNNING)) + + def on_finish(self, status): + self._bigquery_uploader.update_run_status( + self._bigquery_data_set, + self._bigquery_run_status_table, + self._run_id, + status) + + +def _gather_run_info(model_name, dataset_name, run_params, test_id): + """Collect the benchmark run information for the local environment.""" + run_info = { + "model_name": model_name, + "dataset": {"name": dataset_name}, + "machine_config": {}, + "test_id": test_id, + "run_date": datetime.datetime.utcnow().strftime( + _DATE_TIME_FORMAT_PATTERN)} + session_config = None + if "session_config" in run_params: + session_config = run_params["session_config"] + _collect_tensorflow_info(run_info) + _collect_tensorflow_environment_variables(run_info) + _collect_run_params(run_info, run_params) + _collect_cpu_info(run_info) + _collect_gpu_info(run_info, session_config) + _collect_memory_info(run_info) + _collect_test_environment(run_info) + return run_info + + +def _process_metric_to_json( + name, value, unit=None, global_step=None, extras=None): + """Validate the metric data and generate JSON for insert.""" + if not isinstance(value, numbers.Number): + tf.compat.v1.logging.warning( + "Metric value to log should be a number. Got %s", type(value)) + return None + + extras = _convert_to_json_dict(extras) + return { + "name": name, + "value": float(value), + "unit": unit, + "global_step": global_step, + "timestamp": datetime.datetime.utcnow().strftime( + _DATE_TIME_FORMAT_PATTERN), + "extras": extras} + + +def _collect_tensorflow_info(run_info): + run_info["tensorflow_version"] = { + "version": tf.version.VERSION, "git_hash": tf.version.GIT_VERSION} + + +def _collect_run_params(run_info, run_params): + """Log the parameter information for the benchmark run.""" + + def process_param(name, value): + type_check = { + str: {"name": name, "string_value": value}, + int: {"name": name, "long_value": value}, + bool: {"name": name, "bool_value": str(value)}, + float: {"name": name, "float_value": value}, + } + return type_check.get(type(value), + {"name": name, "string_value": str(value)}) + + if run_params: + run_info["run_parameters"] = [ + process_param(k, v) for k, v in sorted(run_params.items())] + + +def _collect_tensorflow_environment_variables(run_info): + run_info["tensorflow_environment_variables"] = [ + {"name": k, "value": v} + for k, v in sorted(os.environ.items()) if k.startswith("TF_")] + + +# The following code is mirrored from tensorflow/tools/test/system_info_lib +# which is not exposed for import. +def _collect_cpu_info(run_info): + """Collect the CPU information for the local environment.""" + cpu_info = {} + + cpu_info["num_cores"] = multiprocessing.cpu_count() + + try: + # Note: cpuinfo is not installed in the TensorFlow OSS tree. + # It is installable via pip. + import cpuinfo # pylint: disable=g-import-not-at-top + + info = cpuinfo.get_cpu_info() + cpu_info["cpu_info"] = info["brand"] + cpu_info["mhz_per_cpu"] = info["hz_advertised_raw"][0] / 1.0e6 + + run_info["machine_config"]["cpu_info"] = cpu_info + except ImportError: + tf.compat.v1.logging.warn( + "'cpuinfo' not imported. CPU info will not be logged.") + + +def _collect_gpu_info(run_info, session_config=None): + """Collect local GPU information by TF device library.""" + gpu_info = {} + local_device_protos = device_lib.list_local_devices(session_config) + + gpu_info["count"] = len([d for d in local_device_protos + if d.device_type == "GPU"]) + # The device description usually is a JSON string, which contains the GPU + # model info, eg: + # "device: 0, name: Tesla P100-PCIE-16GB, pci bus id: 0000:00:04.0" + for d in local_device_protos: + if d.device_type == "GPU": + gpu_info["model"] = _parse_gpu_model(d.physical_device_desc) + # Assume all the GPU connected are same model + break + run_info["machine_config"]["gpu_info"] = gpu_info + + +def _collect_memory_info(run_info): + try: + # Note: psutil is not installed in the TensorFlow OSS tree. + # It is installable via pip. + import psutil # pylint: disable=g-import-not-at-top + vmem = psutil.virtual_memory() + run_info["machine_config"]["memory_total"] = vmem.total + run_info["machine_config"]["memory_available"] = vmem.available + except ImportError: + tf.compat.v1.logging.warn( + "'psutil' not imported. Memory info will not be logged.") + + +def _collect_test_environment(run_info): + """Detect the local environment, eg GCE, AWS or DGX, etc.""" + pass + + +def _parse_gpu_model(physical_device_desc): + # Assume all the GPU connected are same model + for kv in physical_device_desc.split(","): + k, _, v = kv.partition(":") + if k.strip() == "name": + return v.strip() + return None + + +def _convert_to_json_dict(input_dict): + if input_dict: + return [{"name": k, "value": v} for k, v in sorted(input_dict.items())] + else: + return [] diff --git a/utils/logs/logger_test.py b/utils/logs/logger_test.py new file mode 100644 index 0000000..d8abc3a --- /dev/null +++ b/utils/logs/logger_test.py @@ -0,0 +1,366 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Tests for benchmark logger.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import json +import os +import tempfile +import time +import unittest + +import mock +from absl.testing import flagsaver +import tensorflow as tf # pylint: disable=g-bad-import-order + +try: + from google.cloud import bigquery +except ImportError: + bigquery = None + +from utils.flags import core as flags_core +from utils.logs import logger + + +class BenchmarkLoggerTest(tf.test.TestCase): + + @classmethod + def setUpClass(cls): # pylint: disable=invalid-name + super(BenchmarkLoggerTest, cls).setUpClass() + flags_core.define_benchmark() + + def test_get_default_benchmark_logger(self): + with flagsaver.flagsaver(benchmark_logger_type='foo'): + self.assertIsInstance(logger.get_benchmark_logger(), + logger.BaseBenchmarkLogger) + + def test_config_base_benchmark_logger(self): + with flagsaver.flagsaver(benchmark_logger_type='BaseBenchmarkLogger'): + logger.config_benchmark_logger() + self.assertIsInstance(logger.get_benchmark_logger(), + logger.BaseBenchmarkLogger) + + def test_config_benchmark_file_logger(self): + # Set the benchmark_log_dir first since the benchmark_logger_type will need + # the value to be set when it does the validation. + with flagsaver.flagsaver(benchmark_log_dir='/tmp'): + with flagsaver.flagsaver(benchmark_logger_type='BenchmarkFileLogger'): + logger.config_benchmark_logger() + self.assertIsInstance(logger.get_benchmark_logger(), + logger.BenchmarkFileLogger) + + @unittest.skipIf(bigquery is None, 'Bigquery dependency is not installed.') + @mock.patch.object(bigquery, "Client") + def test_config_benchmark_bigquery_logger(self, mock_bigquery_client): + with flagsaver.flagsaver(benchmark_logger_type='BenchmarkBigQueryLogger'): + logger.config_benchmark_logger() + self.assertIsInstance(logger.get_benchmark_logger(), + logger.BenchmarkBigQueryLogger) + + @mock.patch("utils.logs.logger.config_benchmark_logger") + def test_benchmark_context(self, mock_config_benchmark_logger): + mock_logger = mock.MagicMock() + mock_config_benchmark_logger.return_value = mock_logger + with logger.benchmark_context(None): + tf.compat.v1.logging.info("start benchmarking") + mock_logger.on_finish.assert_called_once_with(logger.RUN_STATUS_SUCCESS) + + @mock.patch("utils.logs.logger.config_benchmark_logger") + def test_benchmark_context_failure(self, mock_config_benchmark_logger): + mock_logger = mock.MagicMock() + mock_config_benchmark_logger.return_value = mock_logger + with self.assertRaises(RuntimeError): + with logger.benchmark_context(None): + raise RuntimeError("training error") + mock_logger.on_finish.assert_called_once_with(logger.RUN_STATUS_FAILURE) + + +class BaseBenchmarkLoggerTest(tf.test.TestCase): + + def setUp(self): + super(BaseBenchmarkLoggerTest, self).setUp() + self._actual_log = tf.compat.v1.logging.info + self.logged_message = None + + def mock_log(*args, **kwargs): + self.logged_message = args + self._actual_log(*args, **kwargs) + + tf.compat.v1.logging.info = mock_log + + def tearDown(self): + super(BaseBenchmarkLoggerTest, self).tearDown() + tf.compat.v1.logging.info = self._actual_log + + def test_log_metric(self): + log = logger.BaseBenchmarkLogger() + log.log_metric("accuracy", 0.999, global_step=1e4, extras={"name": "value"}) + + expected_log_prefix = "Benchmark metric:" + self.assertRegexpMatches(str(self.logged_message), expected_log_prefix) + + +class BenchmarkFileLoggerTest(tf.test.TestCase): + + def setUp(self): + super(BenchmarkFileLoggerTest, self).setUp() + # Avoid pulling extra env vars from test environment which affects the test + # result, eg. Kokoro test has a TF_PKG env which affect the test case + # test_collect_tensorflow_environment_variables() + self.original_environ = dict(os.environ) + os.environ.clear() + + def tearDown(self): + super(BenchmarkFileLoggerTest, self).tearDown() + tf.io.gfile.rmtree(self.get_temp_dir()) + os.environ.clear() + os.environ.update(self.original_environ) + + def test_create_logging_dir(self): + non_exist_temp_dir = os.path.join(self.get_temp_dir(), "unknown_dir") + self.assertFalse(tf.io.gfile.isdir(non_exist_temp_dir)) + + logger.BenchmarkFileLogger(non_exist_temp_dir) + self.assertTrue(tf.io.gfile.isdir(non_exist_temp_dir)) + + def test_log_metric(self): + log_dir = tempfile.mkdtemp(dir=self.get_temp_dir()) + log = logger.BenchmarkFileLogger(log_dir) + log.log_metric("accuracy", 0.999, global_step=1e4, extras={"name": "value"}) + + metric_log = os.path.join(log_dir, "metric.log") + self.assertTrue(tf.io.gfile.exists(metric_log)) + with tf.io.gfile.GFile(metric_log) as f: + metric = json.loads(f.readline()) + self.assertEqual(metric["name"], "accuracy") + self.assertEqual(metric["value"], 0.999) + self.assertEqual(metric["unit"], None) + self.assertEqual(metric["global_step"], 1e4) + self.assertEqual(metric["extras"], [{"name": "name", "value": "value"}]) + + def test_log_multiple_metrics(self): + log_dir = tempfile.mkdtemp(dir=self.get_temp_dir()) + log = logger.BenchmarkFileLogger(log_dir) + log.log_metric("accuracy", 0.999, global_step=1e4, extras={"name": "value"}) + log.log_metric("loss", 0.02, global_step=1e4) + + metric_log = os.path.join(log_dir, "metric.log") + self.assertTrue(tf.io.gfile.exists(metric_log)) + with tf.io.gfile.GFile(metric_log) as f: + accuracy = json.loads(f.readline()) + self.assertEqual(accuracy["name"], "accuracy") + self.assertEqual(accuracy["value"], 0.999) + self.assertEqual(accuracy["unit"], None) + self.assertEqual(accuracy["global_step"], 1e4) + self.assertEqual(accuracy["extras"], [{"name": "name", "value": "value"}]) + + loss = json.loads(f.readline()) + self.assertEqual(loss["name"], "loss") + self.assertEqual(loss["value"], 0.02) + self.assertEqual(loss["unit"], None) + self.assertEqual(loss["global_step"], 1e4) + self.assertEqual(loss["extras"], []) + + def test_log_non_number_value(self): + log_dir = tempfile.mkdtemp(dir=self.get_temp_dir()) + log = logger.BenchmarkFileLogger(log_dir) + const = tf.constant(1) + log.log_metric("accuracy", const) + + metric_log = os.path.join(log_dir, "metric.log") + self.assertFalse(tf.io.gfile.exists(metric_log)) + + def test_log_evaluation_result(self): + eval_result = {"loss": 0.46237424, + "global_step": 207082, + "accuracy": 0.9285} + log_dir = tempfile.mkdtemp(dir=self.get_temp_dir()) + log = logger.BenchmarkFileLogger(log_dir) + log.log_evaluation_result(eval_result) + + metric_log = os.path.join(log_dir, "metric.log") + self.assertTrue(tf.io.gfile.exists(metric_log)) + with tf.io.gfile.GFile(metric_log) as f: + accuracy = json.loads(f.readline()) + self.assertEqual(accuracy["name"], "accuracy") + self.assertEqual(accuracy["value"], 0.9285) + self.assertEqual(accuracy["unit"], None) + self.assertEqual(accuracy["global_step"], 207082) + + loss = json.loads(f.readline()) + self.assertEqual(loss["name"], "loss") + self.assertEqual(loss["value"], 0.46237424) + self.assertEqual(loss["unit"], None) + self.assertEqual(loss["global_step"], 207082) + + def test_log_evaluation_result_with_invalid_type(self): + eval_result = "{'loss': 0.46237424, 'global_step': 207082}" + log_dir = tempfile.mkdtemp(dir=self.get_temp_dir()) + log = logger.BenchmarkFileLogger(log_dir) + log.log_evaluation_result(eval_result) + + metric_log = os.path.join(log_dir, "metric.log") + self.assertFalse(tf.io.gfile.exists(metric_log)) + + @mock.patch("utils.logs.logger._gather_run_info") + def test_log_run_info(self, mock_gather_run_info): + log_dir = tempfile.mkdtemp(dir=self.get_temp_dir()) + log = logger.BenchmarkFileLogger(log_dir) + run_info = {"model_name": "model_name", + "dataset": "dataset_name", + "run_info": "run_value"} + mock_gather_run_info.return_value = run_info + log.log_run_info("model_name", "dataset_name", {}) + + run_log = os.path.join(log_dir, "benchmark_run.log") + self.assertTrue(tf.io.gfile.exists(run_log)) + with tf.io.gfile.GFile(run_log) as f: + run_info = json.loads(f.readline()) + self.assertEqual(run_info["model_name"], "model_name") + self.assertEqual(run_info["dataset"], "dataset_name") + self.assertEqual(run_info["run_info"], "run_value") + + def test_collect_tensorflow_info(self): + run_info = {} + logger._collect_tensorflow_info(run_info) + self.assertNotEqual(run_info["tensorflow_version"], {}) + self.assertEqual(run_info["tensorflow_version"]["version"], + tf.version.VERSION) + self.assertEqual(run_info["tensorflow_version"]["git_hash"], + tf.version.GIT_VERSION) + + def test_collect_run_params(self): + run_info = {} + run_parameters = { + "batch_size": 32, + "synthetic_data": True, + "train_epochs": 100.00, + "dtype": "fp16", + "resnet_size": 50, + "random_tensor": tf.constant(2.0) + } + logger._collect_run_params(run_info, run_parameters) + self.assertEqual(len(run_info["run_parameters"]), 6) + self.assertEqual(run_info["run_parameters"][0], + {"name": "batch_size", "long_value": 32}) + self.assertEqual(run_info["run_parameters"][1], + {"name": "dtype", "string_value": "fp16"}) + self.assertEqual(run_info["run_parameters"][2], + {"name": "random_tensor", "string_value": + "Tensor(\"Const:0\", shape=(), dtype=float32)"}) + self.assertEqual(run_info["run_parameters"][3], + {"name": "resnet_size", "long_value": 50}) + self.assertEqual(run_info["run_parameters"][4], + {"name": "synthetic_data", "bool_value": "True"}) + self.assertEqual(run_info["run_parameters"][5], + {"name": "train_epochs", "float_value": 100.00}) + + def test_collect_tensorflow_environment_variables(self): + os.environ["TF_ENABLE_WINOGRAD_NONFUSED"] = "1" + os.environ["TF_OTHER"] = "2" + os.environ["OTHER"] = "3" + + run_info = {} + logger._collect_tensorflow_environment_variables(run_info) + self.assertIsNotNone(run_info["tensorflow_environment_variables"]) + expected_tf_envs = [ + {"name": "TF_ENABLE_WINOGRAD_NONFUSED", "value": "1"}, + {"name": "TF_OTHER", "value": "2"}, + ] + self.assertEqual(run_info["tensorflow_environment_variables"], + expected_tf_envs) + + @unittest.skipUnless(tf.test.is_built_with_cuda(), "requires GPU") + def test_collect_gpu_info(self): + run_info = {"machine_config": {}} + logger._collect_gpu_info(run_info) + self.assertNotEqual(run_info["machine_config"]["gpu_info"], {}) + + def test_collect_memory_info(self): + run_info = {"machine_config": {}} + logger._collect_memory_info(run_info) + self.assertIsNotNone(run_info["machine_config"]["memory_total"]) + self.assertIsNotNone(run_info["machine_config"]["memory_available"]) + + +@unittest.skipIf(bigquery is None, 'Bigquery dependency is not installed.') +class BenchmarkBigQueryLoggerTest(tf.test.TestCase): + + def setUp(self): + super(BenchmarkBigQueryLoggerTest, self).setUp() + # Avoid pulling extra env vars from test environment which affects the test + # result, eg. Kokoro test has a TF_PKG env which affect the test case + # test_collect_tensorflow_environment_variables() + self.original_environ = dict(os.environ) + os.environ.clear() + + self.mock_bq_uploader = mock.MagicMock() + self.logger = logger.BenchmarkBigQueryLogger( + self.mock_bq_uploader, "dataset", "run_table", "run_status_table", + "metric_table", "run_id") + + def tearDown(self): + super(BenchmarkBigQueryLoggerTest, self).tearDown() + tf.io.gfile.rmtree(self.get_temp_dir()) + os.environ.clear() + os.environ.update(self.original_environ) + + def test_log_metric(self): + self.logger.log_metric( + "accuracy", 0.999, global_step=1e4, extras={"name": "value"}) + expected_metric_json = [{ + "name": "accuracy", + "value": 0.999, + "unit": None, + "global_step": 1e4, + "timestamp": mock.ANY, + "extras": [{"name": "name", "value": "value"}] + }] + # log_metric will call upload_benchmark_metric_json in a separate thread. + # Give it some grace period for the new thread before assert. + time.sleep(1) + self.mock_bq_uploader.upload_benchmark_metric_json.assert_called_once_with( + "dataset", "metric_table", "run_id", expected_metric_json) + + @mock.patch("utils.logs.logger._gather_run_info") + def test_log_run_info(self, mock_gather_run_info): + run_info = {"model_name": "model_name", + "dataset": "dataset_name", + "run_info": "run_value"} + mock_gather_run_info.return_value = run_info + self.logger.log_run_info("model_name", "dataset_name", {}) + # log_metric will call upload_benchmark_metric_json in a separate thread. + # Give it some grace period for the new thread before assert. + time.sleep(1) + self.mock_bq_uploader.upload_benchmark_run_json.assert_called_once_with( + "dataset", "run_table", "run_id", run_info) + self.mock_bq_uploader.insert_run_status.assert_called_once_with( + "dataset", "run_status_table", "run_id", "running") + + def test_on_finish(self): + self.logger.on_finish(logger.RUN_STATUS_SUCCESS) + # log_metric will call upload_benchmark_metric_json in a separate thread. + # Give it some grace period for the new thread before assert. + time.sleep(1) + self.mock_bq_uploader.update_run_status.assert_called_once_with( + "dataset", "run_status_table", "run_id", logger.RUN_STATUS_SUCCESS) + + +if __name__ == "__main__": + tf.test.main() diff --git a/utils/logs/metric_hook.py b/utils/logs/metric_hook.py new file mode 100644 index 0000000..f408e3e --- /dev/null +++ b/utils/logs/metric_hook.py @@ -0,0 +1,97 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Session hook for logging benchmark metric.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf # pylint: disable=g-bad-import-order + + +class LoggingMetricHook(tf.estimator.LoggingTensorHook): + """Hook to log benchmark metric information. + + This hook is very similar as tf.train.LoggingTensorHook, which logs given + tensors every N local steps, every N seconds, or at the end. The metric + information will be logged to given log_dir or via metric_logger in JSON + format, which can be consumed by data analysis pipeline later. + + Note that if `at_end` is True, `tensors` should not include any tensor + whose evaluation produces a side effect such as consuming additional inputs. + """ + + def __init__(self, tensors, metric_logger=None, + every_n_iter=None, every_n_secs=None, at_end=False): + """Initializer for LoggingMetricHook. + + Args: + tensors: `dict` that maps string-valued tags to tensors/tensor names, + or `iterable` of tensors/tensor names. + metric_logger: instance of `BenchmarkLogger`, the benchmark logger that + hook should use to write the log. + every_n_iter: `int`, print the values of `tensors` once every N local + steps taken on the current worker. + every_n_secs: `int` or `float`, print the values of `tensors` once every N + seconds. Exactly one of `every_n_iter` and `every_n_secs` should be + provided. + at_end: `bool` specifying whether to print the values of `tensors` at the + end of the run. + + Raises: + ValueError: + 1. `every_n_iter` is non-positive, or + 2. Exactly one of every_n_iter and every_n_secs should be provided. + 3. Exactly one of log_dir and metric_logger should be provided. + """ + super(LoggingMetricHook, self).__init__( + tensors=tensors, + every_n_iter=every_n_iter, + every_n_secs=every_n_secs, + at_end=at_end) + + if metric_logger is None: + raise ValueError("metric_logger should be provided.") + self._logger = metric_logger + + def begin(self): + super(LoggingMetricHook, self).begin() + self._global_step_tensor = tf.compat.v1.train.get_global_step() + if self._global_step_tensor is None: + raise RuntimeError( + "Global step should be created to use LoggingMetricHook.") + if self._global_step_tensor.name not in self._current_tensors: + self._current_tensors[self._global_step_tensor.name] = ( + self._global_step_tensor) + + def after_run(self, unused_run_context, run_values): + # should_trigger is a internal state that populated at before_run, and it is + # using self_timer to determine whether it should trigger. + if self._should_trigger: + self._log_metric(run_values.results) + + self._iter_count += 1 + + def end(self, session): + if self._log_at_end: + values = session.run(self._current_tensors) + self._log_metric(values) + + def _log_metric(self, tensor_values): + self._timer.update_last_triggered_step(self._iter_count) + global_step = tensor_values[self._global_step_tensor.name] + # self._tag_order is populated during the init of LoggingTensorHook + for tag in self._tag_order: + self._logger.log_metric(tag, tensor_values[tag], global_step=global_step) diff --git a/utils/logs/metric_hook_test.py b/utils/logs/metric_hook_test.py new file mode 100644 index 0000000..f250ec1 --- /dev/null +++ b/utils/logs/metric_hook_test.py @@ -0,0 +1,217 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for metric_hook.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tempfile +import time + +import tensorflow as tf # pylint: disable=g-bad-import-order +from tensorflow.python.training import monitored_session # pylint: disable=g-bad-import-order + +from utils.logs import metric_hook +from utils.testing import mock_lib + + +class LoggingMetricHookTest(tf.test.TestCase): + """Tests for LoggingMetricHook.""" + + def setUp(self): + super(LoggingMetricHookTest, self).setUp() + + self._log_dir = tempfile.mkdtemp(dir=self.get_temp_dir()) + self._logger = mock_lib.MockBenchmarkLogger() + + def tearDown(self): + super(LoggingMetricHookTest, self).tearDown() + tf.io.gfile.rmtree(self.get_temp_dir()) + + def test_illegal_args(self): + with self.assertRaisesRegexp(ValueError, "nvalid every_n_iter"): + metric_hook.LoggingMetricHook(tensors=["t"], every_n_iter=0) + with self.assertRaisesRegexp(ValueError, "nvalid every_n_iter"): + metric_hook.LoggingMetricHook(tensors=["t"], every_n_iter=-10) + with self.assertRaisesRegexp(ValueError, "xactly one of"): + metric_hook.LoggingMetricHook( + tensors=["t"], every_n_iter=5, every_n_secs=5) + with self.assertRaisesRegexp(ValueError, "xactly one of"): + metric_hook.LoggingMetricHook(tensors=["t"]) + with self.assertRaisesRegexp(ValueError, "metric_logger"): + metric_hook.LoggingMetricHook(tensors=["t"], every_n_iter=5) + + def test_print_at_end_only(self): + with tf.Graph().as_default(), tf.compat.v1.Session() as sess: + tf.compat.v1.train.get_or_create_global_step() + t = tf.constant(42.0, name="foo") + train_op = tf.constant(3) + hook = metric_hook.LoggingMetricHook( + tensors=[t.name], at_end=True, metric_logger=self._logger) + hook.begin() + mon_sess = monitored_session._HookedSession(sess, [hook]) # pylint: disable=protected-access + sess.run(tf.compat.v1.global_variables_initializer()) + + for _ in range(3): + mon_sess.run(train_op) + self.assertEqual(self._logger.logged_metric, []) + + hook.end(sess) + self.assertEqual(len(self._logger.logged_metric), 1) + metric = self._logger.logged_metric[0] + self.assertRegexpMatches(metric["name"], "foo") + self.assertEqual(metric["value"], 42.0) + self.assertEqual(metric["unit"], None) + self.assertEqual(metric["global_step"], 0) + + def test_global_step_not_found(self): + with tf.Graph().as_default(): + t = tf.constant(42.0, name="foo") + hook = metric_hook.LoggingMetricHook( + tensors=[t.name], at_end=True, metric_logger=self._logger) + + with self.assertRaisesRegexp( + RuntimeError, "should be created to use LoggingMetricHook."): + hook.begin() + + def test_log_tensors(self): + with tf.Graph().as_default(), tf.compat.v1.Session() as sess: + tf.compat.v1.train.get_or_create_global_step() + t1 = tf.constant(42.0, name="foo") + t2 = tf.constant(43.0, name="bar") + train_op = tf.constant(3) + hook = metric_hook.LoggingMetricHook( + tensors=[t1, t2], at_end=True, metric_logger=self._logger) + hook.begin() + mon_sess = monitored_session._HookedSession(sess, [hook]) # pylint: disable=protected-access + sess.run(tf.compat.v1.global_variables_initializer()) + + for _ in range(3): + mon_sess.run(train_op) + self.assertEqual(self._logger.logged_metric, []) + + hook.end(sess) + self.assertEqual(len(self._logger.logged_metric), 2) + metric1 = self._logger.logged_metric[0] + self.assertRegexpMatches(str(metric1["name"]), "foo") + self.assertEqual(metric1["value"], 42.0) + self.assertEqual(metric1["unit"], None) + self.assertEqual(metric1["global_step"], 0) + + metric2 = self._logger.logged_metric[1] + self.assertRegexpMatches(str(metric2["name"]), "bar") + self.assertEqual(metric2["value"], 43.0) + self.assertEqual(metric2["unit"], None) + self.assertEqual(metric2["global_step"], 0) + + def _validate_print_every_n_steps(self, sess, at_end): + t = tf.constant(42.0, name="foo") + + train_op = tf.constant(3) + hook = metric_hook.LoggingMetricHook( + tensors=[t.name], every_n_iter=10, at_end=at_end, + metric_logger=self._logger) + hook.begin() + mon_sess = monitored_session._HookedSession(sess, [hook]) # pylint: disable=protected-access + sess.run(tf.compat.v1.global_variables_initializer()) + mon_sess.run(train_op) + self.assertRegexpMatches(str(self._logger.logged_metric), t.name) + for _ in range(3): + self._logger.logged_metric = [] + for _ in range(9): + mon_sess.run(train_op) + # assertNotRegexpMatches is not supported by python 3.1 and later + self.assertEqual(str(self._logger.logged_metric).find(t.name), -1) + mon_sess.run(train_op) + self.assertRegexpMatches(str(self._logger.logged_metric), t.name) + + # Add additional run to verify proper reset when called multiple times. + self._logger.logged_metric = [] + mon_sess.run(train_op) + # assertNotRegexpMatches is not supported by python 3.1 and later + self.assertEqual(str(self._logger.logged_metric).find(t.name), -1) + + self._logger.logged_metric = [] + hook.end(sess) + if at_end: + self.assertRegexpMatches(str(self._logger.logged_metric), t.name) + else: + # assertNotRegexpMatches is not supported by python 3.1 and later + self.assertEqual(str(self._logger.logged_metric).find(t.name), -1) + + def test_print_every_n_steps(self): + with tf.Graph().as_default(), tf.compat.v1.Session() as sess: + tf.compat.v1.train.get_or_create_global_step() + self._validate_print_every_n_steps(sess, at_end=False) + # Verify proper reset. + self._validate_print_every_n_steps(sess, at_end=False) + + def test_print_every_n_steps_and_end(self): + with tf.Graph().as_default(), tf.compat.v1.Session() as sess: + tf.compat.v1.train.get_or_create_global_step() + self._validate_print_every_n_steps(sess, at_end=True) + # Verify proper reset. + self._validate_print_every_n_steps(sess, at_end=True) + + def _validate_print_every_n_secs(self, sess, at_end): + t = tf.constant(42.0, name="foo") + train_op = tf.constant(3) + + hook = metric_hook.LoggingMetricHook( + tensors=[t.name], every_n_secs=1.0, at_end=at_end, + metric_logger=self._logger) + hook.begin() + mon_sess = monitored_session._HookedSession(sess, [hook]) # pylint: disable=protected-access + sess.run(tf.compat.v1.global_variables_initializer()) + + mon_sess.run(train_op) + self.assertRegexpMatches(str(self._logger.logged_metric), t.name) + + # assertNotRegexpMatches is not supported by python 3.1 and later + self._logger.logged_metric = [] + mon_sess.run(train_op) + self.assertEqual(str(self._logger.logged_metric).find(t.name), -1) + time.sleep(1.0) + + self._logger.logged_metric = [] + mon_sess.run(train_op) + self.assertRegexpMatches(str(self._logger.logged_metric), t.name) + + self._logger.logged_metric = [] + hook.end(sess) + if at_end: + self.assertRegexpMatches(str(self._logger.logged_metric), t.name) + else: + # assertNotRegexpMatches is not supported by python 3.1 and later + self.assertEqual(str(self._logger.logged_metric).find(t.name), -1) + + def test_print_every_n_secs(self): + with tf.Graph().as_default(), tf.compat.v1.Session() as sess: + tf.compat.v1.train.get_or_create_global_step() + self._validate_print_every_n_secs(sess, at_end=False) + # Verify proper reset. + self._validate_print_every_n_secs(sess, at_end=False) + + def test_print_every_n_secs_and_end(self): + with tf.Graph().as_default(), tf.compat.v1.Session() as sess: + tf.compat.v1.train.get_or_create_global_step() + self._validate_print_every_n_secs(sess, at_end=True) + # Verify proper reset. + self._validate_print_every_n_secs(sess, at_end=True) + + +if __name__ == "__main__": + tf.test.main() diff --git a/utils/logs/mlperf_helper.py b/utils/logs/mlperf_helper.py new file mode 100644 index 0000000..c9c0434 --- /dev/null +++ b/utils/logs/mlperf_helper.py @@ -0,0 +1,192 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Wrapper for the mlperf logging utils. + +MLPerf compliance logging is only desired under a limited set of circumstances. +This module is intended to keep users from needing to consider logging (or +install the module) unless they are performing mlperf runs. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +from collections import namedtuple +import json +import os +import re +import subprocess +import sys +import typing + +import tensorflow as tf + +_MIN_VERSION = (0, 0, 10) +_STACK_OFFSET = 2 + +SUDO = "sudo" if os.geteuid() else "" + +# This indirection is used in docker. +DROP_CACHE_LOC = os.getenv("DROP_CACHE_LOC", "/proc/sys/vm/drop_caches") + +_NCF_PREFIX = "NCF_RAW_" + +# TODO(robieta): move line parsing to mlperf util +_PREFIX = r"(?:{})?:::MLPv([0-9]+).([0-9]+).([0-9]+)".format(_NCF_PREFIX) +_BENCHMARK = r"([a-zA-Z0-9_]+)" +_TIMESTAMP = r"([0-9]+\.[0-9]+)" +_CALLSITE = r"\((.+):([0-9]+)\)" +_TAG = r"([a-zA-Z0-9_]+)" +_VALUE = r"(.*)" + +ParsedLine = namedtuple("ParsedLine", ["version", "benchmark", "timestamp", + "callsite", "tag", "value"]) + +LINE_PATTERN = re.compile( + "^{prefix} {benchmark} {timestamp} {callsite} {tag}(: |$){value}?$".format( + prefix=_PREFIX, benchmark=_BENCHMARK, timestamp=_TIMESTAMP, + callsite=_CALLSITE, tag=_TAG, value=_VALUE)) + + +def parse_line(line): # type: (str) -> typing.Optional[ParsedLine] + match = LINE_PATTERN.match(line.strip()) + if not match: + return + + major, minor, micro, benchmark, timestamp = match.groups()[:5] + call_file, call_line, tag, _, value = match.groups()[5:] + + return ParsedLine(version=(int(major), int(minor), int(micro)), + benchmark=benchmark, timestamp=timestamp, + callsite=(call_file, call_line), tag=tag, value=value) + + +def unparse_line(parsed_line): # type: (ParsedLine) -> str + version_str = "{}.{}.{}".format(*parsed_line.version) + callsite_str = "({}:{})".format(*parsed_line.callsite) + value_str = ": {}".format(parsed_line.value) if parsed_line.value else "" + return ":::MLPv{} {} {} {} {} {}".format( + version_str, parsed_line.benchmark, parsed_line.timestamp, callsite_str, + parsed_line.tag, value_str) + + +def get_mlperf_log(): + """Shielded import of mlperf_log module.""" + try: + import mlperf_compliance + + def test_mlperf_log_pip_version(): + """Check that mlperf_compliance is up to date.""" + import pkg_resources + version = pkg_resources.get_distribution("mlperf_compliance") + version = tuple(int(i) for i in version.version.split(".")) + if version < _MIN_VERSION: + tf.compat.v1.logging.warning( + "mlperf_compliance is version {}, must be >= {}".format( + ".".join([str(i) for i in version]), + ".".join([str(i) for i in _MIN_VERSION]))) + raise ImportError + return mlperf_compliance.mlperf_log + + mlperf_log = test_mlperf_log_pip_version() + + except ImportError: + mlperf_log = None + + return mlperf_log + + +class Logger(object): + """MLPerf logger indirection class. + + This logger only logs for MLPerf runs, and prevents various errors associated + with not having the mlperf_compliance package installed. + """ + class Tags(object): + def __init__(self, mlperf_log): + self._enabled = False + self._mlperf_log = mlperf_log + + def __getattr__(self, item): + if self._mlperf_log is None or not self._enabled: + return + return getattr(self._mlperf_log, item) + + def __init__(self): + self._enabled = False + self._mlperf_log = get_mlperf_log() + self.tags = self.Tags(self._mlperf_log) + + def __call__(self, enable=False): + if enable and self._mlperf_log is None: + raise ImportError("MLPerf logging was requested, but mlperf_compliance " + "module could not be loaded.") + + self._enabled = enable + self.tags._enabled = enable + return self + + def __enter__(self): + pass + + def __exit__(self, exc_type, exc_val, exc_tb): + self._enabled = False + self.tags._enabled = False + + @property + def log_file(self): + if self._mlperf_log is None: + return + return self._mlperf_log.LOG_FILE + + @property + def enabled(self): + return self._enabled + + def ncf_print(self, key, value=None, stack_offset=_STACK_OFFSET, + deferred=False, extra_print=False, prefix=_NCF_PREFIX): + if self._mlperf_log is None or not self.enabled: + return + self._mlperf_log.ncf_print(key=key, value=value, stack_offset=stack_offset, + deferred=deferred, extra_print=extra_print, + prefix=prefix) + + def set_ncf_root(self, path): + if self._mlperf_log is None: + return + self._mlperf_log.ROOT_DIR_NCF = path + + +LOGGER = Logger() +ncf_print, set_ncf_root = LOGGER.ncf_print, LOGGER.set_ncf_root +TAGS = LOGGER.tags + + +def clear_system_caches(): + if not LOGGER.enabled: + return + ret_code = subprocess.call( + ["sync && echo 3 | {} tee {}".format(SUDO, DROP_CACHE_LOC)], + shell=True) + + if ret_code: + raise ValueError("Failed to clear caches") + + +if __name__ == "__main__": + tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.INFO) + with LOGGER(True): + ncf_print(key=TAGS.RUN_START) diff --git a/utils/misc/__init__.py b/utils/misc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/misc/distribution_utils.py b/utils/misc/distribution_utils.py new file mode 100644 index 0000000..9a4b74f --- /dev/null +++ b/utils/misc/distribution_utils.py @@ -0,0 +1,91 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Helper functions for running models in a distributed setting.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf + + +def get_distribution_strategy(num_gpus, + all_reduce_alg=None, + turn_off_distribution_strategy=False): + """Return a DistributionStrategy for running the model. + Args: + num_gpus: Number of GPUs to run this model. + all_reduce_alg: Specify which algorithm to use when performing all-reduce. + See tf.contrib.distribute.AllReduceCrossDeviceOps for available + algorithms. If None, DistributionStrategy will choose based on device + topology. + turn_off_distribution_strategy: when set to True, do not use any + distribution strategy. Note that when it is True, and num_gpus is + larger than 1, it will raise a ValueError. + Returns: + tf.contrib.distribute.DistibutionStrategy object. + Raises: + ValueError: if turn_off_distribution_strategy is True and num_gpus is + larger than 1 + """ + if num_gpus == 0: + if turn_off_distribution_strategy: + return None + else: + return tf.contrib.distribute.OneDeviceStrategy("device:CPU:0") + elif num_gpus == 1: + if turn_off_distribution_strategy: + return None + else: + return tf.contrib.distribute.OneDeviceStrategy("device:GPU:0") + elif turn_off_distribution_strategy: + raise ValueError("When {} GPUs are specified, " + "turn_off_distribution_strategy flag cannot be set to" + "True.".format(num_gpus)) + else: # num_gpus > 1 and not turn_off_distribution_strategy + devices = ["device:GPU:%d" % i for i in range(num_gpus)] + if all_reduce_alg: + return tf.distribute.MirroredStrategy( + devices=devices, + cross_device_ops=tf.contrib.distribute.AllReduceCrossDeviceOps( + all_reduce_alg, num_packs=2)) + else: + return tf.distribute.MirroredStrategy(devices=devices) + + +def per_device_batch_size(batch_size, num_gpus): + """For multi-gpu, batch-size must be a multiple of the number of GPUs. + Note that distribution strategy handles this automatically when used with + Keras. For using with Estimator, we need to get per GPU batch. + Args: + batch_size: Global batch size to be divided among devices. This should be + equal to num_gpus times the single-GPU batch_size for multi-gpu training. + num_gpus: How many GPUs are used with DistributionStrategies. + Returns: + Batch size per device. + Raises: + ValueError: if batch_size is not divisible by number of devices + """ + if num_gpus <= 1: + return batch_size + + remainder = batch_size % num_gpus + if remainder: + err = ("When running with multiple GPUs, batch size " + "must be a multiple of the number of available GPUs. Found {} " + "GPUs with a batch size of {}; try --batch_size={} instead." + ).format(num_gpus, batch_size, batch_size - remainder) + raise ValueError(err) + return int(batch_size / num_gpus) \ No newline at end of file diff --git a/utils/misc/distribution_utils_test.py b/utils/misc/distribution_utils_test.py new file mode 100644 index 0000000..9948402 --- /dev/null +++ b/utils/misc/distribution_utils_test.py @@ -0,0 +1,65 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +""" Tests for distribution util functions.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf # pylint: disable=g-bad-import-order + +from utils.misc import distribution_utils + + +class GetDistributionStrategyTest(tf.test.TestCase): + """Tests for get_distribution_strategy.""" + def test_one_device_strategy_cpu(self): + ds = distribution_utils.get_distribution_strategy(num_gpus=0) + self.assertEquals(ds.num_replicas_in_sync, 1) + self.assertEquals(len(ds.extended.worker_devices), 1) + self.assertIn('CPU', ds.extended.worker_devices[0]) + + def test_one_device_strategy_gpu(self): + ds = distribution_utils.get_distribution_strategy(num_gpus=1) + self.assertEquals(ds.num_replicas_in_sync, 1) + self.assertEquals(len(ds.extended.worker_devices), 1) + self.assertIn('GPU', ds.extended.worker_devices[0]) + + def test_mirrored_strategy(self): + ds = distribution_utils.get_distribution_strategy(num_gpus=5) + self.assertEquals(ds.num_replicas_in_sync, 5) + self.assertEquals(len(ds.extended.worker_devices), 5) + for device in ds.extended.worker_devices: + self.assertIn('GPU', device) + + +class PerDeviceBatchSizeTest(tf.test.TestCase): + """Tests for per_device_batch_size.""" + + def test_batch_size(self): + self.assertEquals( + distribution_utils.per_device_batch_size(147, num_gpus=0), 147) + self.assertEquals( + distribution_utils.per_device_batch_size(147, num_gpus=1), 147) + self.assertEquals( + distribution_utils.per_device_batch_size(147, num_gpus=7), 21) + + def test_batch_size_with_remainder(self): + with self.assertRaises(ValueError): + distribution_utils.per_device_batch_size(147, num_gpus=5) + + +if __name__ == "__main__": + tf.test.main() diff --git a/utils/misc/model_helpers.py b/utils/misc/model_helpers.py new file mode 100644 index 0000000..c112bac --- /dev/null +++ b/utils/misc/model_helpers.py @@ -0,0 +1,93 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Miscellaneous functions that can be called by models.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import numbers + +import tensorflow as tf +from tensorflow.python.util import nest + + +def past_stop_threshold(stop_threshold, eval_metric): + """Return a boolean representing whether a model should be stopped. + + Args: + stop_threshold: float, the threshold above which a model should stop + training. + eval_metric: float, the current value of the relevant metric to check. + + Returns: + True if training should stop, False otherwise. + + Raises: + ValueError: if either stop_threshold or eval_metric is not a number + """ + if stop_threshold is None: + return False + + if not isinstance(stop_threshold, numbers.Number): + raise ValueError("Threshold for checking stop conditions must be a number.") + if not isinstance(eval_metric, numbers.Number): + raise ValueError("Eval metric being checked against stop conditions " + "must be a number.") + + if eval_metric >= stop_threshold: + tf.compat.v1.logging.info( + "Stop threshold of {} was passed with metric value {}.".format( + stop_threshold, eval_metric)) + return True + + return False + + +def generate_synthetic_data( + input_shape, input_value=0, input_dtype=None, label_shape=None, + label_value=0, label_dtype=None): + """Create a repeating dataset with constant values. + + Args: + input_shape: a tf.TensorShape object or nested tf.TensorShapes. The shape of + the input data. + input_value: Value of each input element. + input_dtype: Input dtype. If None, will be inferred by the input value. + label_shape: a tf.TensorShape object or nested tf.TensorShapes. The shape of + the label data. + label_value: Value of each input element. + label_dtype: Input dtype. If None, will be inferred by the target value. + + Returns: + Dataset of tensors or tuples of tensors (if label_shape is set). + """ + # TODO(kathywu): Replace with SyntheticDataset once it is in contrib. + element = input_element = nest.map_structure( + lambda s: tf.constant(input_value, input_dtype, s), input_shape) + + if label_shape: + label_element = nest.map_structure( + lambda s: tf.constant(label_value, label_dtype, s), label_shape) + element = (input_element, label_element) + + return tf.data.Dataset.from_tensors(element).repeat() + + +def apply_clean(flags_obj): + if flags_obj.clean and tf.io.gfile.exists(flags_obj.model_dir): + tf.compat.v1.logging.info("--clean flag set. Removing existing model dir:" + " {}".format(flags_obj.model_dir)) + tf.io.gfile.rmtree(flags_obj.model_dir) diff --git a/utils/misc/model_helpers_test.py b/utils/misc/model_helpers_test.py new file mode 100644 index 0000000..62a99bf --- /dev/null +++ b/utils/misc/model_helpers_test.py @@ -0,0 +1,121 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +""" Tests for Model Helper functions.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import tensorflow as tf # pylint: disable=g-bad-import-order + +from utils.misc import model_helpers + + +class PastStopThresholdTest(tf.test.TestCase): + """Tests for past_stop_threshold.""" + + def test_past_stop_threshold(self): + """Tests for normal operating conditions.""" + self.assertTrue(model_helpers.past_stop_threshold(0.54, 1)) + self.assertTrue(model_helpers.past_stop_threshold(54, 100)) + self.assertFalse(model_helpers.past_stop_threshold(0.54, 0.1)) + self.assertFalse(model_helpers.past_stop_threshold(-0.54, -1.5)) + self.assertTrue(model_helpers.past_stop_threshold(-0.54, 0)) + self.assertTrue(model_helpers.past_stop_threshold(0, 0)) + self.assertTrue(model_helpers.past_stop_threshold(0.54, 0.54)) + + def test_past_stop_threshold_none_false(self): + """Tests that check None returns false.""" + self.assertFalse(model_helpers.past_stop_threshold(None, -1.5)) + self.assertFalse(model_helpers.past_stop_threshold(None, None)) + self.assertFalse(model_helpers.past_stop_threshold(None, 1.5)) + # Zero should be okay, though. + self.assertTrue(model_helpers.past_stop_threshold(0, 1.5)) + + def test_past_stop_threshold_not_number(self): + """Tests for error conditions.""" + with self.assertRaises(ValueError): + model_helpers.past_stop_threshold("str", 1) + + with self.assertRaises(ValueError): + model_helpers.past_stop_threshold("str", tf.constant(5)) + + with self.assertRaises(ValueError): + model_helpers.past_stop_threshold("str", "another") + + with self.assertRaises(ValueError): + model_helpers.past_stop_threshold(0, None) + + with self.assertRaises(ValueError): + model_helpers.past_stop_threshold(0.7, "str") + + with self.assertRaises(ValueError): + model_helpers.past_stop_threshold(tf.constant(4), None) + + +class SyntheticDataTest(tf.test.TestCase): + """Tests for generate_synthetic_data.""" + + def test_generate_synethetic_data(self): + input_element, label_element = tf.compat.v1.data.make_one_shot_iterator( + model_helpers.generate_synthetic_data(input_shape=tf.TensorShape([5]), + input_value=123, + input_dtype=tf.float32, + label_shape=tf.TensorShape([]), + label_value=456, + label_dtype=tf.int32)).get_next() + + with self.test_session() as sess: + for n in range(5): + inp, lab = sess.run((input_element, label_element)) + self.assertAllClose(inp, [123., 123., 123., 123., 123.]) + self.assertEquals(lab, 456) + + def test_generate_only_input_data(self): + d = model_helpers.generate_synthetic_data( + input_shape=tf.TensorShape([4]), + input_value=43.5, + input_dtype=tf.float32) + + element = tf.compat.v1.data.make_one_shot_iterator(d).get_next() + self.assertFalse(isinstance(element, tuple)) + + with self.test_session() as sess: + inp = sess.run(element) + self.assertAllClose(inp, [43.5, 43.5, 43.5, 43.5]) + + def test_generate_nested_data(self): + d = model_helpers.generate_synthetic_data( + input_shape={'a': tf.TensorShape([2]), + 'b': {'c': tf.TensorShape([3]), 'd': tf.TensorShape([])}}, + input_value=1.1) + + element = tf.compat.v1.data.make_one_shot_iterator(d).get_next() + self.assertIn('a', element) + self.assertIn('b', element) + self.assertEquals(len(element['b']), 2) + self.assertIn('c', element['b']) + self.assertIn('d', element['b']) + self.assertNotIn('c', element) + + with self.test_session() as sess: + inp = sess.run(element) + self.assertAllClose(inp['a'], [1.1, 1.1]) + self.assertAllClose(inp['b']['c'], [1.1, 1.1, 1.1]) + self.assertAllClose(inp['b']['d'], 1.1) + + +if __name__ == "__main__": + tf.test.main() diff --git a/utils/testing/__init__.py b/utils/testing/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/testing/integration.py b/utils/testing/integration.py new file mode 100644 index 0000000..2c0bb9a --- /dev/null +++ b/utils/testing/integration.py @@ -0,0 +1,65 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Helper code to run complete models from within python. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import os +import shutil +import sys +import tempfile + +from absl import flags + +from utils.flags import core as flags_core + + +def run_synthetic(main, tmp_root, extra_flags=None, synth=True, max_train=1): + """Performs a minimal run of a model. + + This function is intended to test for syntax errors throughout a model. A + very limited run is performed using synthetic data. + + Args: + main: The primary function used to exercise a code path. Generally this + function is ".main(argv)". + tmp_root: Root path for the temp directory created by the test class. + extra_flags: Additional flags passed by the caller of this function. + synth: Use synthetic data. + max_train: Maximum number of allowed training steps. + """ + + extra_flags = [] if extra_flags is None else extra_flags + + model_dir = tempfile.mkdtemp(dir=tmp_root) + + args = [sys.argv[0], "--model_dir", model_dir, "--train_epochs", "1", + "--epochs_between_evals", "1"] + extra_flags + + if synth: + args.append("--use_synthetic_data") + + if max_train is not None: + args.extend(["--max_train_steps", str(max_train)]) + + try: + flags_core.parse_flags(argv=args) + main(flags.FLAGS) + finally: + if os.path.exists(model_dir): + shutil.rmtree(model_dir) diff --git a/utils/testing/mock_lib.py b/utils/testing/mock_lib.py new file mode 100644 index 0000000..ee4de3c --- /dev/null +++ b/utils/testing/mock_lib.py @@ -0,0 +1,36 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +"""Mock objects and related functions for testing.""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + + +class MockBenchmarkLogger(object): + """This is a mock logger that can be used in dependent tests.""" + + def __init__(self): + self.logged_metric = [] + + def log_metric(self, name, value, unit=None, global_step=None, + extras=None): + self.logged_metric.append({ + "name": name, + "value": float(value), + "unit": unit, + "global_step": global_step, + "extras": extras}) diff --git a/utils/testing/pylint.rcfile b/utils/testing/pylint.rcfile new file mode 100644 index 0000000..6a41947 --- /dev/null +++ b/utils/testing/pylint.rcfile @@ -0,0 +1,169 @@ +[MESSAGES CONTROL] +disable=R,W, + bad-option-value + +[REPORTS] +# Tells whether to display a full report or only the messages +reports=no + +# Activate the evaluation score. +score=no + +[BASIC] + +# Regular expression matching correct argument names +argument-rgx=^[a-z][a-z0-9_]*$ + +# Regular expression matching correct attribute names +attr-rgx=^_{0,2}[a-z][a-z0-9_]*$ + +# Regular expression matching correct class attribute names +class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ + +# Regular expression matching correct class names +class-rgx=^_?[A-Z][a-zA-Z0-9]*$ + +# Regular expression matching correct constant names +const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=10 + +# Regular expression matching correct function names +function-rgx=^(?:(?P_?[A-Z][a-zA-Z0-9]*)|(?P_?[a-z][a-z0-9_]*))$ + +# Good variable names which should always be accepted, separated by a comma +good-names=main,_ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=^[a-z][a-z0-9_]*$ + +# Regular expression matching correct method names +method-rgx=^(?:(?P__[a-z0-9_]+__|next)|(?P_{0,2}[A-Z][a-zA-Z0-9]*)|(?P_{0,2}[a-z][a-z0-9_]*)|(setUp|tearDown))$ + +# Regular expression matching correct module names +module-rgx=^(_?[a-z][a-z0-9_]*)|__init__|PRESUBMIT|PRESUBMIT_unittest$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=(__.*__|main|.*ArgParser) + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=^[a-z][a-z0-9_]*$ + +[TYPECHECK] + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules=absl, absl.*, official, *, tensorflow, tensorflow.*, LazyLoader, google, google.cloud.* + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + +# This is deprecated, because it is not used anymore. +#ignore-iface-methods= + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls,class_ + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=StandardError,Exception,BaseException + + +[FORMAT] + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=99999 + +# List of optional constructs for which whitespace checking is disabled +no-space-check= + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=yes + +# Allow URLs and comment type annotations to exceed the max line length as neither can be easily +# split across lines. +ignore-long-lines=^\s*(?:(# )??$|# type:) + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_) + +# Tells whether we should check for unused import in __init__ files. +init-import=no diff --git a/utils/testing/reference_data.py b/utils/testing/reference_data.py new file mode 100644 index 0000000..68e6517 --- /dev/null +++ b/utils/testing/reference_data.py @@ -0,0 +1,336 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""TensorFlow testing subclass to automate numerical testing. + +Reference tests determine when behavior deviates from some "gold standard," and +are useful for determining when layer definitions have changed without +performing full regression testing, which is generally prohibitive. This class +handles the symbolic graph comparison as well as loading weights to avoid +relying on random number generation, which can change. + +The tests performed by this class are: + +1) Compare a generated graph against a reference graph. Differences are not + necessarily fatal. +2) Attempt to load known weights for the graph. If this step succeeds but + changes are present in the graph, a warning is issued but does not raise + an exception. +3) Perform a calculation and compare the result to a reference value. + +This class also provides a method to generate reference data. + +Note: + The test class is responsible for fixing the random seed during graph + definition. A convenience method name_to_seed() is provided to make this + process easier. + +The test class should also define a .regenerate() class method which (usually) +just calls the op definition function with test=False for all relevant tests. + +A concise example of this class in action is provided in: + official/utils/testing/reference_data_test.py +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import argparse +import hashlib +import json +import os +import shutil +import sys + +import numpy as np +import tensorflow as tf +from tensorflow.python import pywrap_tensorflow + + +class BaseTest(tf.test.TestCase): + """TestCase subclass for performing reference data tests.""" + + def regenerate(self): + """Subclasses should override this function to generate a new reference.""" + raise NotImplementedError + + @property + def test_name(self): + """Subclass should define its own name.""" + raise NotImplementedError + + @property + def data_root(self): + """Use the subclass directory rather than the parent directory. + + Returns: + The path prefix for reference data. + """ + return os.path.join(os.path.split( + os.path.abspath(__file__))[0], "reference_data", self.test_name) + + ckpt_prefix = "model.ckpt" + + @staticmethod + def name_to_seed(name): + """Convert a string into a 32 bit integer. + + This function allows test cases to easily generate random fixed seeds by + hashing the name of the test. The hash string is in hex rather than base 10 + which is why there is a 16 in the int call, and the modulo projects the + seed from a 128 bit int to 32 bits for readability. + + Args: + name: A string containing the name of a test. + + Returns: + A pseudo-random 32 bit integer derived from name. + """ + seed = hashlib.md5(name.encode("utf-8")).hexdigest() + return int(seed, 16) % (2**32 - 1) + + @staticmethod + def common_tensor_properties(input_array): + """Convenience function for matrix testing. + + In tests we wish to determine whether a result has changed. However storing + an entire n-dimensional array is impractical. A better approach is to + calculate several values from that array and test that those derived values + are unchanged. The properties themselves are arbitrary and should be chosen + to be good proxies for a full equality test. + + Args: + input_array: A numpy array from which key values are extracted. + + Returns: + A list of values derived from the input_array for equality tests. + """ + output = list(input_array.shape) + flat_array = input_array.flatten() + output.extend([float(i) for i in + [flat_array[0], flat_array[-1], np.sum(flat_array)]]) + return output + + def default_correctness_function(self, *args): + """Returns a vector with the concatenation of common properties. + + This function simply calls common_tensor_properties() for every element. + It is useful as it allows one to easily construct tests of layers without + having to worry about the details of result checking. + + Args: + *args: A list of numpy arrays corresponding to tensors which have been + evaluated. + + Returns: + A list of values containing properties for every element in args. + """ + output = [] + for arg in args: + output.extend(self.common_tensor_properties(arg)) + return output + + def _construct_and_save_reference_files( + self, name, graph, ops_to_eval, correctness_function): + """Save reference data files. + + Constructs a serialized graph_def, layer weights, and computation results. + It then saves them to files which are read at test time. + + Args: + name: String defining the run. This will be used to define folder names + and will be used for random seed construction. + graph: The graph in which the test is conducted. + ops_to_eval: Ops which the user wishes to be evaluated under a controlled + session. + correctness_function: This function accepts the evaluated results of + ops_to_eval, and returns a list of values. This list must be JSON + serializable; in particular it is up to the user to convert numpy + dtypes into builtin dtypes. + """ + data_dir = os.path.join(self.data_root, name) + + # Make sure there is a clean space for results. + if os.path.exists(data_dir): + shutil.rmtree(data_dir) + os.makedirs(data_dir) + + # Serialize graph for comparison. + graph_bytes = graph.as_graph_def().SerializeToString() + expected_file = os.path.join(data_dir, "expected_graph") + with tf.io.gfile.GFile(expected_file, "wb") as f: + f.write(graph_bytes) + + with graph.as_default(): + init = tf.compat.v1.global_variables_initializer() + saver = tf.compat.v1.train.Saver() + + with self.test_session(graph=graph) as sess: + sess.run(init) + saver.save(sess=sess, save_path=os.path.join(data_dir, self.ckpt_prefix)) + + # These files are not needed for this test. + os.remove(os.path.join(data_dir, "checkpoint")) + os.remove(os.path.join(data_dir, self.ckpt_prefix + ".meta")) + + # ops are evaluated even if there is no correctness function to ensure + # that they can be evaluated. + eval_results = [op.eval() for op in ops_to_eval] + + if correctness_function is not None: + results = correctness_function(*eval_results) + result_json = os.path.join(data_dir, "results.json") + with tf.io.gfile.GFile(result_json, "w") as f: + json.dump(results, f) + tf_version_json = os.path.join(data_dir, "tf_version.json") + with tf.io.gfile.GFile(tf_version_json, "w") as f: + json.dump([tf.version.VERSION, tf.version.GIT_VERSION], f) + + def _evaluate_test_case(self, name, graph, ops_to_eval, correctness_function): + """Determine if a graph agrees with the reference data. + + Args: + name: String defining the run. This will be used to define folder names + and will be used for random seed construction. + graph: The graph in which the test is conducted. + ops_to_eval: Ops which the user wishes to be evaluated under a controlled + session. + correctness_function: This function accepts the evaluated results of + ops_to_eval, and returns a list of values. This list must be JSON + serializable; in particular it is up to the user to convert numpy + dtypes into builtin dtypes. + """ + data_dir = os.path.join(self.data_root, name) + + # Serialize graph for comparison. + graph_bytes = graph.as_graph_def().SerializeToString() + expected_file = os.path.join(data_dir, "expected_graph") + with tf.io.gfile.GFile(expected_file, "rb") as f: + expected_graph_bytes = f.read() + # The serialization is non-deterministic byte-for-byte. Instead there is + # a utility which evaluates the semantics of the two graphs to test for + # equality. This has the added benefit of providing some information on + # what changed. + # Note: The summary only show the first difference detected. It is not + # an exhaustive summary of differences. + differences = pywrap_tensorflow.EqualGraphDefWrapper( + graph_bytes, expected_graph_bytes).decode("utf-8") + + with graph.as_default(): + init = tf.compat.v1.global_variables_initializer() + saver = tf.compat.v1.train.Saver() + + with tf.io.gfile.GFile(os.path.join(data_dir, "tf_version.json"), "r") as f: + tf_version_reference, tf_git_version_reference = json.load(f) # pylint: disable=unpacking-non-sequence + + tf_version_comparison = "" + if tf.version.GIT_VERSION != tf_git_version_reference: + tf_version_comparison = ( + "Test was built using: {} (git = {})\n" + "Local TensorFlow version: {} (git = {})" + .format(tf_version_reference, tf_git_version_reference, + tf.version.VERSION, tf.version.GIT_VERSION) + ) + + with self.test_session(graph=graph) as sess: + sess.run(init) + try: + saver.restore(sess=sess, save_path=os.path.join( + data_dir, self.ckpt_prefix)) + if differences: + tf.compat.v1.logging.warn( + "The provided graph is different than expected:\n {}\n" + "However the weights were still able to be loaded.\n{}".format( + differences, tf_version_comparison) + ) + except: # pylint: disable=bare-except + raise self.failureException( + "Weight load failed. Graph comparison:\n {}{}" + .format(differences, tf_version_comparison)) + + eval_results = [op.eval() for op in ops_to_eval] + if correctness_function is not None: + results = correctness_function(*eval_results) + result_json = os.path.join(data_dir, "results.json") + with tf.io.gfile.GFile(result_json, "r") as f: + expected_results = json.load(f) + self.assertAllClose(results, expected_results) + + def _save_or_test_ops(self, name, graph, ops_to_eval=None, test=True, + correctness_function=None): + """Utility function to automate repeated work of graph checking and saving. + + The philosophy of this function is that the user need only define ops on + a graph and specify which results should be validated. The actual work of + managing snapshots and calculating results should be automated away. + + Args: + name: String defining the run. This will be used to define folder names + and will be used for random seed construction. + graph: The graph in which the test is conducted. + ops_to_eval: Ops which the user wishes to be evaluated under a controlled + session. + test: Boolean. If True this function will test graph correctness, load + weights, and compute numerical values. If False the necessary test data + will be generated and saved. + correctness_function: This function accepts the evaluated results of + ops_to_eval, and returns a list of values. This list must be JSON + serializable; in particular it is up to the user to convert numpy + dtypes into builtin dtypes. + """ + + ops_to_eval = ops_to_eval or [] + + if test: + try: + self._evaluate_test_case( + name=name, graph=graph, ops_to_eval=ops_to_eval, + correctness_function=correctness_function + ) + except: + tf.compat.v1.logging.error("Failed unittest {}".format(name)) + raise + else: + self._construct_and_save_reference_files( + name=name, graph=graph, ops_to_eval=ops_to_eval, + correctness_function=correctness_function + ) + + +class ReferenceDataActionParser(argparse.ArgumentParser): + """Minimal arg parser so that test regeneration can be called from the CLI.""" + + def __init__(self): + super(ReferenceDataActionParser, self).__init__() + self.add_argument( + "--regenerate", "-regen", + action="store_true", + help="Enable this flag to regenerate test data. If not set unit tests" + "will be run." + ) + + +def main(argv, test_class): + """Simple switch function to allow test regeneration from the CLI.""" + flags = ReferenceDataActionParser().parse_args(argv[1:]) + if flags.regenerate: + if sys.version_info[0] == 2: + raise NameError("\nPython2 unittest does not support being run as a " + "standalone class.\nAs a result tests must be " + "regenerated using Python3.\n" + "Tests can be run under 2 or 3.") + test_class().regenerate() + else: + tf.test.main() diff --git a/utils/testing/reference_data/reference_data_test/dense/expected_graph b/utils/testing/reference_data/reference_data_test/dense/expected_graph new file mode 100644 index 0000000000000000000000000000000000000000..335628111e08e14eb90588b57998d1dd7e719c9b GIT binary patch literal 5996 zcmbW5O>Y}T7{@)f;$&&EEiVn zdtWVs#WZ|kAG>tz&e9Jf^D?Y`ZO>zu?(EZHkM?PI*xenHPN&nwKXV<{oahvXci-*7 zEuey_h36NBCWWDmeHaqjG5a{5GeBep?2{$~Pj9gi5QyQD5W%NuZV$55=!pAjNHaG_ z&<|bLp=%dkJpZq6UI21GNssfCd`Sk~pxui-_^6h67|&?$vv_9IDq^41pLu@ag~0uq ztwlViy?gQ8Xw0o<6+@`3-i1xRFtm5uacXnWRs9(o-IuW8`Nwf&MXnz%0}2oAz_Vv_ z_u&n5j3r684(ttan~-FynA@<94+Gb+e0zc01Q(v!GsN72mkyVHWP83FByZtI0%O+2 z^mZ6}k9|7GJ`K*lA!L=fi$m9%FAtAA|1s^pMamkOS72Zr&h0Ry6ByHN9JBWU>&e`K zP2B6AV@Ix)WrFkJ0c^9Qrh#iaba>#ne&j``d_3yT;5!1>&_?J^kKDj_=hFkDu=&c|t8l#mm)0!dMKkx@pU%^t8)Ylx--bD%T%OqTDkJdH*7u0w*hMLB+ zFAdciH;XT8Qm*up!y^%Yc4AHZ*_Z16QTwiDGiRP1a@z*ge;HXI>o+%xh|JSrqrQK=J^wbz;6UDH94gH zyWV_mXw}0|NTFJdmLy&V@O;NTu}Y3T1*jtC`a`4@+}b(w4E7QTUk) z<}-oj^xRiGcV71b(fv!{rusTGGM(;vt^_%S)Y#r<>F^5tRa_60*0UtL3`mvcERgMV zhO?B>o*E!y>sT-DxaVC(om4o=Q%L1Gfwz@6S4Mhzsp|xfty`&*Lh#=c*yMxfPBh`t zo*ZH%ky7!}-tLK&Bgwt~R{|404w3qVFb(yZ>l8$t{XWkjwdy9}>MsPW69t^X^`aVj z47d`#G0eo$3JN6b^7J5pH(K0t9N6l#enj8_#hp+D{v_~Pv(uafH7jE%rBbanfCVy$P6j`GXQWTW1WqD01;u9K$i%;D@ zGGf_c0ws>pjWjJNF1t;l_x!iC#D5bI^6yf9gfDW!MJu`4iV}q|aw283t+JAn;%;|n F{0|&%8MOca literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/reference_data_test/dense/model.ckpt.data-00000-of-00001 b/utils/testing/reference_data/reference_data_test/dense/model.ckpt.data-00000-of-00001 new file mode 100644 index 0000000000000000000000000000000000000000..514037b565c0d5652887cb9d955f4859e3fde12f GIT binary patch literal 76 zcmZQzKm`}%*6v%N)@QpVgmwP`+b+8c@uvG7vOR1o^n&b9)Hd6JR9AV_?r$h$w7bCh S+cqIq#NO$HuDyb(l^p>0{}#po literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/reference_data_test/dense/model.ckpt.index b/utils/testing/reference_data/reference_data_test/dense/model.ckpt.index new file mode 100644 index 0000000000000000000000000000000000000000..29935cfdace6f1411cffb9504e2e35781b94dbb9 GIT binary patch literal 254 zcmZQzVB=tvV&Y(A;Nnk7%_~mTPs&Uz=3o?J5n|$C(GW1*nIC?GjZHK=wJ0w&2Pn<~ z6lVg-DF|pNm`a@KOk-sij5kEq#;9PR!D70sTF{CEO(V!qMgYJl0PUD59)*6YMILUf$ej29ju`3 z+C7(8mX#g41y!EROu2pNzzN_QR4CO7sIvm9pMy)#q7~C$03g2su<2g_ZzuT)2)x4X z+5|TXZNsHB+%AOPugd8Lkh@Gz0Xhq`5c~-3u0tD8$pWl(=b!I$0#_yFIvt^Nn~uEF zO4ziVlWN6E;I4!frU5$FY2f`Su7qkv_(8pdMsYLfoN-?2F;wMTe!2&!OEpOoKM_ht zF~U8IB^w3e{)o2BiWc61W7qhEWQR6k%OqpLeZ@lK6Z7*q(-67?o8w3&Oezs)ukb;j zm8a;RX)T`=)|R(d;P=*4Wpt?)eh`gcqZJ4>>dUnU<46=Qax

ZWuZvz0WyA%-s literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/reference_data_test/uniform_random/results.json b/utils/testing/reference_data/reference_data_test/uniform_random/results.json new file mode 100644 index 0000000..76b54aa --- /dev/null +++ b/utils/testing/reference_data/reference_data_test/uniform_random/results.json @@ -0,0 +1 @@ +[0.9872556924819946] \ No newline at end of file diff --git a/utils/testing/reference_data/reference_data_test/uniform_random/tf_version.json b/utils/testing/reference_data/reference_data_test/uniform_random/tf_version.json new file mode 100644 index 0000000..5674d7d --- /dev/null +++ b/utils/testing/reference_data/reference_data_test/uniform_random/tf_version.json @@ -0,0 +1 @@ +["1.8.0-dev20180325", "v1.7.0-rc1-750-g6c1737e6c8"] \ No newline at end of file diff --git a/utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-1_width-8_channels-4/expected_graph b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-1_width-8_channels-4/expected_graph new file mode 100644 index 0000000000000000000000000000000000000000..611e8aafe9c72422d2ec705a43a5c8c59c8150fc GIT binary patch literal 27274 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 i%kWZP@H$wty7BuNssBq3~s&=49zNE#DDOo&M~ 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 literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-1_width-8_channels-4/model.ckpt.index b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_projection_version-1_width-8_channels-4/model.ckpt.index new file mode 100644 index 0000000000000000000000000000000000000000..056b74bec758b0d79b0a59cb3d668121cb436cd5 GIT binary patch literal 824 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 Y`wKY((ER!}JuVysyE@*18MxB_0p1QP7g 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 N(y_%~SLc_){{rP!P%Que literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/model.ckpt.data-00000-of-00001 b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/model.ckpt.data-00000-of-00001 new file mode 100644 index 0000000000000000000000000000000000000000..888b17101a4f910afba423b2ffaa9cb5ac42dfc7 GIT binary patch literal 32932 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 literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/model.ckpt.index b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/model.ckpt.index new file mode 100644 index 0000000000000000000000000000000000000000..a89441ad472c9c23940bab147e0b439f465a37ce GIT binary patch literal 628 zcmZQzVB=tvV&Y(Akl;^BEJ@CY&&w~$P0Y!xN-W9D&(lvzElK2H6k-tqD$!suT~;k< zCBiD0o|u~p7gk^aN;EFLr7glOn44dgnU@})o0^yh)1|-x)hEp%Se96nnV6TH3YX)7 zY87S|j5kEFOhEvuO$^;S5vU$9?AA#@^~qwfP6nz~giX*0*)|ph1q}hyo%!K6&}?H- zFaSy@o1fl|)ixFd2dF+w+gKC=pjsJtWs~#s%8XL#FL9CAje>R<+fXcmPjj6f569-i`M;8DoT zD=00AFGMrc`R_TxV))z7PD|{VBrS|;RoTr8@g3W-ERW`&!m1+ literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/results.json b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/results.json new file mode 100644 index 0000000..b408696 --- /dev/null +++ b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/results.json @@ -0,0 +1 @@ +[32, 8, 8, 4, 0.1677999496459961, 0.7767924070358276, 4089.44189453125, 32, 8, 8, 4, 0.8615571856498718, 1.1359407901763916, 5806.876953125] \ No newline at end of file diff --git a/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/tf_version.json b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/tf_version.json new file mode 100644 index 0000000..8cf1ef0 --- /dev/null +++ b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-1_width-8_channels-4/tf_version.json @@ -0,0 +1 @@ +["1.8.0-dev20180408", "v1.7.0-1345-gb874783ccd"] \ No newline at end of file diff --git a/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/expected_graph b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/expected_graph new file mode 100644 index 0000000000000000000000000000000000000000..a1cdadead5d4c172abc78c49d48f684cdda4d12f GIT binary patch literal 20432 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

OUiimkSO(9fblLJJhM) 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 literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/results.json b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/results.json new file mode 100644 index 0000000..fa61c20 --- /dev/null +++ b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/results.json @@ -0,0 +1 @@ +[32, 8, 8, 4, 0.8239736557006836, 0.3485994338989258, 4108.87548828125, 32, 8, 8, 4, 0.16798323392868042, -0.2975311279296875, 2860.068359375] \ No newline at end of file diff --git a/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/tf_version.json b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/tf_version.json new file mode 100644 index 0000000..8cf1ef0 --- /dev/null +++ b/utils/testing/reference_data/resnet/batch-size-32_bottleneck_version-2_width-8_channels-4/tf_version.json @@ -0,0 +1 @@ +["1.8.0-dev20180408", "v1.7.0-1345-gb874783ccd"] \ No newline at end of file diff --git a/utils/testing/reference_data/resnet/batch-size-32_building_projection_version-1_width-8_channels-4/expected_graph b/utils/testing/reference_data/resnet/batch-size-32_building_projection_version-1_width-8_channels-4/expected_graph new file mode 100644 index 0000000000000000000000000000000000000000..f1fa1eb5de287004dcd6b0cdcec63c3e796a4802 GIT binary patch literal 20709 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 literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/resnet/batch-size-32_building_projection_version-1_width-8_channels-4/model.ckpt.data-00000-of-00001 b/utils/testing/reference_data/resnet/batch-size-32_building_projection_version-1_width-8_channels-4/model.ckpt.data-00000-of-00001 new file mode 100644 index 0000000000000000000000000000000000000000..4cdfc7ea4474018e0e6b7956ebdb78597f1d47d5 GIT binary patch literal 36736 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�O+hHlUQqu{R@l={UOxnHNEFu)K+ z#t%qoa*N!y8KG@YyFnH8DH= zE-7}!QSp@#q`vS`sErJeHFp;!>L}4*yqdMGE~5BCnPfSUpSP)=6nU3m5cjvZY_k|G zif(Y!w&J~UJt}%X6Jfm*MKtG>l&8LzybFssFT*+BT?^@=EAMIhBos;gSWA$JU@;gb zd>L8OA2!^X&_j4}?@VFeevpjc@SPLpLj3~?zY9~i3r|j&UM6t;CkUAUA`IV?D(riy z(RUdv_tlf*XXb$$xEp`*RnCtxhy2MH2HJj9*unkj8SPY&@}tOP-BLf2d&~1B-rHm- z?y-&1M{?K0mIvhjFdC81IhT>-jd<@NQ2d=qWmz-HWQ-l(pJIf}e%k2T*$dh|O}RJB zlw=m%GkrIgtmh?jPZnpxtGU;H+XX5zzDD{7HX%QCEBW#sFB>scl1;cIl{NA1@z-0S zSa(ts$$zERLlRWUTcOCcA?0_2$Y;|ExSMQ)MJ&%jORI${=bR+%KSI^>7ee0gN|YUA zKitWbn zV98P;|9b@$+h3Am|J7o4dm*K74~6pCJ&O6f1|Nkp=q|I)kKdB-?|Z0a|8ThbtwHqa za9oUWg#C$pD$X1w+@j(rZiIxu9089OIZRh^SNpk5lpnhU)n?|1dT5LMf(=x4i8)mE zZ&c2lt6<(BaxCv_^Btl7@Pg9U&qs1!wUFKFLMp9JslM+is^7qSMQ8;3=zFMP@j%p% z;k<_KZ4t5G7CybbAscdrYR|}oGdH*P3eVDbWLEKU0c}1x5 zUJ6BKk?`}1fx6Y1wFREz^};FpiU*ALM8WY~EIAnaa&I_iEHm+uF-UHbN*T`cC_abv|iKSK`QaAO7#ogJETP+vKzJ^E$Va9c10u>H1 zhW+AtQXaJsM$6;LC4CiErI{mg&vayWJmF_>1gS2pm&y#r(Jt=9Zu{vsO6akK5@MrC zcJjUO^=l)aCU>N<_pN;Vw=k=m1oy`=FuAydOh+!`uVGFa9~1H}XV0*up2{C7apy`9 z_f|6_$X)i8%yYOuC*})#Qrg|sX#Lt7K@C34dYZC!^o01X2TAV%XOn#{NEJp4DdWp& zYO|A*D#AnZU)d8G&sQQW$%r#X+&iIoMAd7O$nYgI$(kCGe{2oaj+Tj!ceg^;{gZP< z%1%lh!J0|upQKRyPRX$aLg)4FlLC5f z6$RI7sPTa(X~mUM+=5bSc+GdgH1ga%iubcagSi9zHARLy!8eMtwQnBNs&(IRA4~{j zPJZNigL`BDx=vxYGeuO>Cz3s0;9PU2h%$m!BdhBKXkG+zcaKDQ_xzB2eg;W1*P`s} zLJEEcGP9V6;8UBaq>mSh*3Ll8?D5cySpYNJV3=n|L#?_a;!aM0>m>G$*E{1%(mE8y zo~8&Xoilk)$?By8(mBsm-K>8!7cIdzWt~qW<`OD!9TNf0;AMHSbtQ zHbd@#9b_?`&$@mY6wqcu&7b++EK@xgN&CpMa|(N2E_i&&1V*RdQ$hEMu(`buk5}_t zy`!8a53xr%-vK8xJWowij9{I?9-woDwxkG4?TZV%4zPA7@=7e~MO zkd1Gk0Pnxa^Y<_^<~|I+ty7T_dWmXgFG1wf)!fH8m3Ma;LW~bk^W`0+y~r2R>E5Ux z#(mT{K?{ZkBD?Jj1+4xc5;FT!(Rt36uU|->VP)ibWIK0d%;J1;8`XaMo2XK7Mq=+! z-W^|%>R1FyqIq7*U_LR&5Y4}EFQsV~8FEHh*=;%+BbrFIdWQI@*+)wLIXZkl9+!V& z-z(z|S$gqVT3Y995K&E$w|bzfH{Ste^px`QrbA&mmK=Gnj~Tj~IiQ!K`1;?XQ_kH# z!N)0Ax=t-h=C}^CZlSa!dox?4oQUD>LSHiYX#?qX|C0*F@8ey?4$>iA?>)R@71+7RY^nbGKA zo;u?`wOXd zKOzq{TZ>t$8cCxVBT^7EA8lI>b7UD>}V>5%(;Ul)rvU8CASnFM2?Z z>8B`c)_F3!)=qv?Y|t=sDR;|HA!Uzj?qY4HVx0$)_M~vw_uNkPy|dY0Uq-<_cTspR z=8gCr7*G)_G^X3hC^?<>4;zdaoyE{xdPEJXx#)Px9ihr2qG=gthJJ1!`IUp@#{IKi zQ}p5DycoIBKayPvbI}L9u~q2}+lPY?TzHTY?mr-{OInb%&lL83N=Pg4IjL`4AX%4T zlFp4aobUdNl?CU+Gw$=vrcA#s2r(Ljb86m2{%}EoJu}Dr z@-&TQUU?P2JNV8rMOYuo+EPI3>-NGo&=?68szMH?FkKe*$ zT7RV78+O->t9+ z!Qah9Q1wL`eS^EdqN}JQf*I~0TU5mC5-FLt>Ei5ggjs~aYSJK>XjxN$ZWj@n!;E>2 zKWc-Xk+t&=RAT1`-(m~0bU919gE>F-bp+o#Rzx~iNASL_q1v9^#nu?U=VtsVQua26 z&*dn@JgT9Kp?nuu@(n7i>54W}Ytk(KQ&b0EBJ%{!jL4%QUv!58-u_9lB{h=Q-?NZ^ z^sVsQ@f{`28v#ucGC^g@_a>!KDKDApN7!`%n`~nU_$#A zO1I!WjCs{yzKdX2@OqRl{f!En`0GZ+lFBYl%1>X<-3~8Ut7c~ICqMFd*AKZFy&#`` zl^!40V^7e5j1-$;GIjub8-hrE?y_)6Od`!x_SMz=&qcgnIPW9byJW=tQ za~P~UD6+EqBX>_L_YEwO)aUG}h_$#{oB#OzaexwYc9GW4zw^1Iq~=G=s1M-``kOn{ zH1r#UX#t8ooy@AvH87Z&$}#YoOem9Ha{h2K+Qt`k*!;JfGkVZGv=t_Zyp zj;xyps5DB!JpCE+zs9>x<2mv87rv)cyP`ms*rii{-gY#6*-RQ4rUvIlt$Jt3nk zJ}2TSDxjTIIw9h5#dzqpvzPkd9}#SqK>K%(gPYAFSSV&8c6})C3ui?}MI8BaPAE2z z`;^mekoOK9g!JtL?H{LeHwB3rVBE6rm99ldl6h7we+$XIRELD=H z$xtK)myqj_fw&g744QQjs87E}$zG#zl>d8a*&T}ebr;FMG?3KhHvd(HGM{b6}xD{wfRd*)jP3ird~5VDF{6P-Uuvbs%WuA`_Zh4uUS21GHoSny{w z+#I$d^>i?nujF~^wlBg)+o1a21is&R4t%8Xh=`P+wul#5Z-=2Wcmq;=j#F~ieIo1W z6a>^nkSb@pq*HAL!()7JsLcTGrS8RdadLM`-$1JEu?B@azjj=m0?+(oWS{e%x@vmE zZ2Wu5Xz7Ds{cVtMnI;+oNAvf_eWf)$ab?XCc&1x1%WZ`EuKm%jw;Zayp~7Y+@9NRq zS@`{Bp(y%Mq!rrHgX43dYI`j?d4`~3ONX$o5;%F=2$xqeqg(z9Nk{EqKAicxz7g<0 zu?AT|F-RydgC{)`)%zb&V#-5O4fhhV{9>`7k@Ex*Ymj|o3!a5>wj<|*NPom<;yZ$B zFwbQFEv2~M%c!7^+4uKz=?dTbm~VZR)DPc?sGfXJ@X$y7QZ+S>`Hwyx8ViRI z*511@qxjcn3fl0DjN-Xd=)(lW4jzQ`r2~=Bbs2S>S&VuuPmJE}hMJ7cC^^5E8eOKCNAkSt>Pt}SKxb8@NWd?ZGTFuN>r?{b>t-;F_zcNm%*XCf^*n@WypM6$MdT!8d{;(~&vivBbNgn*XKafxC95`KzwZR3zp_E{wN#-T@ldLbd`VXS zGGDATf+lAIZYuxRP0bmkX>C+0%plk52ZMi>4l+MaY6yzSr5FLVda+JJp!V)>c!+1I|$o+bi6ryd@W(6*!l;5q@5$Nb+(( z;XWf$Kc6BJ!e>C`zgj%#ZGze-cPM&|`~P?1I&|%Y+@#kOGV>CJwV0DCWVdMRT23nV zK)3$Fyq%ptT|CTe&UC(Cxxf!8i)1i+#-9P@IQZ86OZhK9k$HRi6UHLQ}u7C6WGt>E*;Clqsj~c{9+m+DI3DcTFC6BgD}r&*d_(4Z?`Umcp zd%1^m&hvYoTji__$>V^ii+ui3j&|0qqGsLw?07RMnkBbKfTHSdyZ5kTe`H0S~@P zFgUM7o3;!`8V#s>8;9ZFi_x{P4@$So@nk;b7?GCfMjpMNnraqM3NW~*9OP*R_z;mm zex~F>In;YKrDC>WH^*CjZs2!j(D3zp3E-)(csMlz$bQ zfm1&s$$PvR2DS`ylDZr)MNM-;_W|5Wx>KXI3>2^WA#9tG$wj{){xXjmb_s=vq+^k5 ramuzHV|I`?eSI$T-J!qNd4vm9I6!^I%+fb&C{vy^8ohc<!%8j8F^?V}%B0jRr>3#_s6_Y@G7I zFhNnu3{(ojP^Byy4V84lnR#{6(OXf zUV~X#Gqk=Q1i5E$s{FMqy4!H19qkSW-XM(n-5!Noey{CscEVdN_aRhK%ptlqhkRMY zS7Y>=bm!qPe%T8HFY-|y4oaE#7(x?5}S%?|4>z$`Mo9r%OLOW@a*34=TAhaTSkT07dN zcnMyC`Rs$uUg!t3aXkp5L3?ml#$!G|e2IX&=?}JUdl9ZeMDi(r(C$XfZGUISZ(fhu zgEqSu_L|)&?DN&|AYV>%8?(5a;`Pav6t0L1LgFP@q>scB7fv7<0p23;VBxKjt&ehB zPp>6&YwTMcNS+OUAh1}7Or=J#4334WQyba|Voaz{!z0BaDeNj1sUl4HVgc_q!+}gY zq+WL{?{F)BT=4D;oTjG=dB-M@Y44F^(hWgOWllAj=eon+gH(oF-_6+pB*QxLf3J^ z?h845kHC{9fc97n6m5kPfepR&Q-Z%2z@^LM_{*n~Is7((M-A{vJU)ntt8w{11)gWb zO)jFk!WQ8(2yWE=^?LH)Pn*M}Kif$OF%L9(Xo3rZMnT z<9N6V59#EK$M9B7N4Z~vCEYOBFenc;ShV~i2Fr9-{_NxfbZ1P6<&--_M;mv=!Z>Ae zx^plS?)KZAE+)X|zW>k1-knKU1alBBb z%NEyVKC=#uK#y)B_k_yRGO z4A~cFs$(O>;kjDQV1CJ+Az{lap zhy0+q>jwe43z45c!`<`6jTJn%W#@0~e5SUv$c|gf{<0Be9iE0d4o}%XCqXAoCvdO` z@TLI4PJzEh;3A&n?X`mDYhf=6J5p3U=nbPSJZi&Z!1%PwXA7oP~B1zW?6m?zXHD}uwo&W*s=DZ3&-A+Tvjq(R`?a>M&=SF zhxnE%aBNqD4SsZH4@mf>oYQLI=4%3Kg#ptKmV(4PT!Y0ON`(P_Mqoigqvn({R>wm1 z7D_QMF}w;?hO3-%$Z#znz;&UBpxucJ3|!(E(vFO5>C*CjI$Jz$})XUxGQnPzCETT z2m+rcaLGZTEqA&f#*x#Fi$(l8 ze`un+>|T)f!0<}~Clo}gPgM~+R;I90khy351owZbtrXv?!nOheAIPYMySmCA_UBo% zUy*%m;%}T$SR+gGfAh0n!S$v|&=H+=7eiyz#jqmW)>&Ahi$!%2K7~YE_%6|&obsEc zHMF0J#@0V{s}TM|w1?Xqow}ZM=gjylP#eOt#8s z;md_|)R*0P6s&2giRp+@-xGq4kHQ&xx`dAT_J(g0aP+Gq*WjRZCFtn0A1FD#vZJ*# z#n@CTmWWZrM7_o})Je^4scS7a;hwg219=z&Quh85xvIiqmA*@Q&1P!wQeZu<<$-hY zNx?-Img)HtE@ozuH^?6snmzM~v`v~YbN|5d1pT+ZV2Xy!AXRxu0y4l#>WtAxJXjAJRqbNh00L9c>- z=;Cs-{{H5ja!qGSyafek{kXi~tm{6+Co(QIUYbt9eKISvof8EGXAK%rzR?w&HTPpT z3wBTF3eNhQ#V!GVEOa6Y39<#}@&4Bo3oAG0tHlDITg`ia6rRvKZ$&0lj&&ap{)oZM zD9PXnx$WQ>(;Eq8GcqT*?w0|GR`tOw=rSaEX360XD~$CcXl^HkV>|&`S8xgbrf*!z zuMpHzrd`O)@0w#`louTq)dP!52i{HJtf z=dyg#Y?NDP$1I~dw8w9dQpsGKg+W(CNpXbFkp&^uX!s4~AjWkz{NrQ)dfrwS1Tr-m zGZ@A-8nfZzAC0NS6;7binhhUcQY-twI~>AnjfUS{u4A!#@EVQTV~S2hjmGTh#IRiE z(i#mPFk7SH*GeUmYcxiWG*KC%Mq~JdX^n;t$NHUkOwF%Jsip0*$|sQpRi5~_l`KwB zyB;z%8jeU!9aY4lMk5}`X7K86VrA$mH5!3JIpR=ZTLH<`Xj;vKtkJYs3YhJK7&#nK yq>;q2FVe793Cm`P`Lr1V`^#Tmzwth}1a<5cOjc2<$59f<8%vUpLaj@+hVwtuH)c2h literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/resnet/batch-size-32_building_projection_version-2_width-8_channels-4/model.ckpt.data-00000-of-00001 b/utils/testing/reference_data/resnet/batch-size-32_building_projection_version-2_width-8_channels-4/model.ckpt.data-00000-of-00001 new file mode 100644 index 0000000000000000000000000000000000000000..ab8bffdee826bd55a61df9ca07745a0938234bed GIT binary patch literal 36544 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಴55NC~!|2^9<%b+3FGV^6uiZ?KA~;FJPW6A?RvsA;*H1 z2<|r>T01Ky{QE6s3rui)cp~nPh(%`gKA?4oTgPkp9z^NLu|jS%qCES&RVLuS0P^e;Mirb*H-clT=`^nKU)Jh%Yih?JI)e zoCYfEzK47+g~5K)I0&o%VY_^EpxM>QOtZttH_!s2l{&0r+gjAieUPPdgq~y!Mt*`F zeAD|Q=G1URgbzlS^f6`CCLwp~dCD8(4)ytcl9F!I(f)D;n(e1joY4o;jdX-oQ^cBQ z9ip1aE0EmYL89?$DmiwElqaTA_>td8(dI<@3)i7B(g6nM=BRl0jQmgEA;F=4Nn1G& zxds1{q2YfdT$@1;56y?*xGjA2XOr^mamrPnA;SmZNGkb878`rR{M-)OrPB-HocEK$ z9O4BhS#{SyXszo=zw0G64qAgM=X_Eg&1YRzjM#~;eJhm)~>+_>E?^9CA;W6XVq~9rzwZ)lh$7sB8$((oE>i{GhG0; zUMI*VYB5AxLr9>1LQ-`u2uB;bK`>$tDRxYx(4kK$?W7^z{WcA~D^Rs9Y*t*ddo|T+3bA)*4L3T$JkK~H)l-zWfk{yI-%+SN3H$73Ww}I+> zli+w~9Nv1nz%z??O%zcGb)U}j9zXJ2?g8!7p%m{w1ls)REcC$7lu)>WeAbOYxb`R& zuF>QEOGPQh2~fp}p{%Q9(i68Oq6|f zmrCMHA+o>Ank$k~7qyc-bG#r=o667XtyGnoLSA8qD7oZ1Wq;^}UDbo3c+?Yd&xa$r z$r))nnUp`TgY4IAL~zbRR1N4NyKQdpEa?UFxreAM#fY=hl_ZP&%qmaLKy$xPWc;NI zmzUS6Xv=b{zBdha%XUx_&!&=BbwmAEIpjYrCFNh`Ozc0Migkyeh%Mi&j3-CQXnhapl}?1*yPnc^#3Md223h0ksCB75?0@ru z;J{%?@n`-SuB~8>bMi!aJfh>+M#lw#S;A`ZD0L3gcl`cSXV}G9GoTdul87g_%Mx`0kDDzWKo-39xsq{V* zoSQCDrkXPK)Lmq5^@ya#Ly(Z!#VoJIz;wcNbVj>U=D;PibFv2-7Rqs|yED&|udvoU zJG{6!9_u2Pp=iiE>N`6SCG9sT!*U7&=MN$EY%LQ@7sAdZ3i5bM-fLQfD%X?bC4NLA z*FQ%d+c9; zbdxL+4Rk`$$Q*Lf^MUrJBU4$0R*{H%K z5?VEq^vxYg3ghg?e>@Tn{K3?7Zb*_#Zjjo)kxXaVQvA0*klIdQ(%Zw~)-{j}6Hk!+ zyRqo9;@ev;>4;=MhWN?8Xg|r>z3IO+Lq7&F-7M*N&rwiL+yJdn9&_}2NcHE>kalSSg@%`r z-9&dXJ;eF6(R|9dZ3d(M1c6NcJ(Frld(SKAFRJ!Ez*Qy-U^N8K_?+f=iGd)LTz5 zg?%sbo<0^;(=$l_^kS~V9?}yBo?l%1Oi77`@Tr(WqMMm4X1q16J`jqc%j=OkOal3h z6BN_L*~ukhV#hO-emaN*%e^G(vvX+MwiS@+c2KFaG3WlNELdDiHnqH8IAbI_OT$na zGzf9@gtnz5B6UqX!&LwKQE~Pg4A8d6k*VP}UN&KgtB*}~jcz(5oVu&MJ zKCXiP+F-2n?2eMzTqh2FPHiS9NE|Vq_Yr0>*`_r({uAFroN;tMAA#m;?r7t^iK>80 zM9!fQ+{tB1pE&Ak!e1YQ(3zZwiuIM`m^~kTZPy@H$=~1ac4YWb2eKEvsO#ibD&x$; zZO||(sXju&PX$bNmBD4p4k~Hp8TJ)N_+0**UMvVlQ4c2yEPTpx2dODdR}8Hqf35=? zNstju27?mN&~lHer_RRC{LzSuu|tQW4^(9{U>Vnq`+Yz7gc*}C+Jb!2ekNJaU}_w? z8tFO{s3>VGt$r+n?pa$T6s@Pu?^5N6XNWGZ+>NMxbwk8){DPpn&>he1A9^)3cnM1;`|JY7z z8B8B;r;3F;=;B2w1irJV;7|(1RQySbOGT2}qifN2@jla^X#m}%VNhKBQA)r2gT=a!yxJQGg4@b-m%f)E{nRFH=FJKV^@wMH}yK zwxxBFdh}6A<5o+QJvKl>XBO)i_<+hDPU3yy70j;fI3+B-N?toBptNc(NzTL|FfxtB zY1*kRmS=~L`r?qn5TYd>%(%x^Qsjh4qHGr-)cQ2#@m%P9{%VL>1Sux!QF#1e$`zcZ z#=Wy(tl_=dhFPd>T8TP`25Q~vz`H9dGU?B|TjyP2wtB}5j1KdMmtH50w)I5X$U2JLCLr23SG3`wn}n@5w#IAl!FJ zM%=m06r;FJ+UkuwGq6G3i7ZlI9mKg;0BIBF!0^lr?AvLMk00bPowuJ9PctNzopLBv zjgz#E`M|Ujj*)ctYu2^a8ZntmAY50^cDi$}oxhJFG#2n5d4$pyc%bQLbJUM2rMNIZ zY~g3^smtr&$>(yzRta3Lm67=m5htDkmy&AI99oMnO+z85(|Y9iRuN4Z35}Zx zUyI+FT}UW=4)TmT`4@U?=MM3h$)w3>;hy3hdm>Rr9{t#Mu-Pz&r5v zj#y_u8?umltZ~!4(Hm(pN5q!Rg5c~o62+vTYqve}cip6R zCuhh<@1g(sE2+dE+Cf}vc{;L|pLhpo(O%w5tz#R9k^?8;O;K#Gj!Bncld)t3ZLC1<& zNGaxf?noU~Bzr>O@9QDk%ROvcJ5}AUBIo&1DD`%b$omLu9<-15rPWL^d^ihBoR3pa z2cYc_-f@^3P3tNcGF$hO^uuP#>{-VmPmVwc*Y>j0{Bt}rnl;BbkaWm8dboi5)kSfX za-V;O`+7lO$vgb%Zz;y+5w-DNw>f74C1tiK7zhZaGmqlR401_7sJ{+kzP)+Yc|!*(_p!W3{)%LUIqX!UB?LdO zrSNV%|580AiK>ahjtzn~s*`1X4M#);?~sj8W&THk`OmzQWj*Mi)Q9~47(FIoBiEQq zM!|7+7iIRaCei+hXyiQ4Bqyf zee->H{F4!4^nW4k*v0fcXDyz@n?X}6qnKktc-f^<_K|S7vLM+18jaPwx2WX$OX`1y z66?gcatH;Jt|9{4T{oN!`o>s+cwoxzg_xveOfG&j*q;qC=9KzJ)~o z{GQtgLVK?%sGk;#8Ici?)^BCn=~Gcqe}gQIgb;myPd+AN$?^GMv`MB>LF_v!k*i5n z>jlx_pV+#iBavnJgVd=DSi@_3n1pkWT{szUzs`ZQmfsoapMd7tLu5KEmns8;AeybF zvd^zcaZN^Fx=lu2+<4^MT%jj5D-r)!Z)lwOC$R0LBw?PE)SJSXIAtiLzxy-S@~Oz! zV+uFZZYZ!drlTfugr3VH0e=m}wVv$g_)#!h(G3|{8Px(!{_Oy>`!E4jRWr%!$|X{%Ho*MRBHopp4&zhDso8ZJTw5%V zzD<{c(?3&U&Q$n3X`#9wMZD+HK$@{_%KN$LR-LlO7G9TVF&k}d&e-T%UJeZ9gf%^?z{&wnk8*GK+?s> zDBQV)!fX;@+++xa-bqQ@=T_DfWPs#>@5#iPcX;W7%NboBt72CHPTu_n-~Na7daZD_flcP8fcHbA=T%_&@=20%_&`~ z^WIKxc~3ntJQDu1Y*62KA072`NA&Uj5X>!=gieg7X1NZOe(Mou`7irW(l@!z_xoI=rx6+s^oCe9l|)T}Okr&yDZ01-S;IT& z3eOs%V9N@X;f%+m@+PfBhj`8(z-t<(@VWbpmm-Q z@NOmaJ7aMC7b*0MKT?s7nz__*4pbRLabq_?Y_gm(9gHb0lVE3I2eo<{Og9Zf&Of^; z?tL#P_ww#jyg%=7@qGC1Gp6G6RTD9b0xwNMMtm60n{27&FE3P$jHcMWD^YMbh`f^j zBSG6$lFjZU>A7nXg~LAnSvHfog*W`)UnigP6nZ<#1M*Pbkv#T|v|hKEOYHB|YLri- z-R;pjtBlH~4uyPpG&N_~;!e;86yE3ha^^J;&2k5vN}7Rm!$MM~G3H|Unv5#@qIA($ z%5bVDvB5#6oyPsn^B*j2DDPOF4?@||U_>RmK$P*B@@G#&V>cR+|S4Q#kDo#a#%S_=b z{i%p^R&z#=L$?~THSo=DK>LbPx_`#5(YRR8osz8&vf7S1N0J8!6Igo<|#&5@Pp z2;;OA_DNZugUh)F z>L87GcT&gyED>_gtXqAZoVV~jJTr#(W_#A4JqkbQ!fk~kUbrOTOJ*3#+;}(i z`fRw*c89@r3$$G$od0(j^wg8kJmD6leKJFW@py!6916!5on*LlC7Qscu0^fS7x!s<7s z*j4NXE%)}B-x|n$=`iH}@|ueBE;Hlw$taEKprr1R5b}FxrhWENQI&{PQ`aEHdOQ?I zd?gt_^ShYW{3$%LmI@!L=uYGWxD9jSj8BJ(jr&2E_>l#Mo?f1fX-HPQnf%)V&{eq>`?dx`sjgLeMB= zZNfCxET4?v@cGCrG^1>O-$QR1&m>21u7BkOHLjV6^AiTav2X_|x?QKjf-uBsvM8^d zv*9P-S=WnGWXL^D=!h7SUgY}W-^C=e;_vImJd(fTcb}X&cl|aB0U_%kddly}-Tj+2 zAK?r@T0w?t-pO1bM2AlXb@}nkIq(jx+pr4Bv2oB(^QYS2wb1UVVEPAn$Cr17qYgBY za)S$JanGn|$Pre5V=}o<9R{CC`IIde!e`lX^2{2AXSe$leB}3-6B7N72f;paotC?0}>U+0sW`}k59e&;p$0i}2Br}Ur* z5@7(8J2oQpM8yB;=#1lHOusfhNRlK;k}ya@5|Y&1=Nd`cw2UN47)g>ONkU~dX+ule zmL$Z6v@H{9W77^o2q8(5v`I?{Y11a}`MrPRQ)Zs$zR$VNb$t(=cq>H+d)O)rZQOkZV+LKfTmj9=&$7;Uk;rnmKtm4OBctgINrt9~sBiz0<-2~AuHYW)bAwD__f-^R zT&2(?&eYA~n&XEjlu>w*YDW|an-iv#XWtJFo2EfA<3B3yJ&60^s}x{41Cmqc9Bm^z zsP=|J?DXSISjA~7?zfyX!0acRq>zo-tq-%n^JJ3oH`OGJN6~{W{G3~(*j^XDz1UCs z_bfU)G!(TjOQ<7m7IvHWM#cV(k(p^duti%ch@NM?Hty-QiZhNMT#2vglu)zKqvee&Hmwg zcpdA*KI36 z%WMQ{Um?w%9u&l$>a1e+W=6OobaW}#DYJQw+E-kes)gbKP2^EA0!gRig=%RQYvh-x z)T^FCWxFY%(-v*tcSAu;3e~2~A{Y0iXtWs3x-l~hDqX3lf$vJ4T$0sIhSgyo_?gCV zHqHnYYU*8wG#|B&JmYqwKhlky1b|2<|P-6Nlr z=>|eS#~M-P(MWx`mn;=CsNCTTW%a&GZAx8K|J5Cm!S&=Bagc@wN5Iyjlk}R(DdSKO z8Bnc|cCs&LnjTdwoQslilPJq#BemMHXZ*25mh|jBl}rn#$nPvLe25%7zm0^$0M28~ z&=H!Ulc>4$wg~7k7)FQxpwj+B@OnEl6x@c2COKzoHuL$vqc4@NeL%IR>&a#18q{Ua zK-EBIFPw}M83zAQ)~< zbj5U`zVkvT;#)*w?^TF1tfau9wv_VbFr_wj;j{9R(73l#OI0Uno?WMjHlq-AKA&t~ zSCd}gRM>i-q8QmQ7~K0JYX9I`vqUBA=9-}ScUww$JP&3)FOzMN1!{GMk*(V`DqQrA zOu6RH+?+?9r^h=LDP$pNZPym57bdha^A8 zQF>VqdI9ICd>4CV(1;2ZOR^iFV|V8&w3H|-`1y=bI#8-)5Tw@4q; z@j%WV)7OVdx;<4y9_mj+PD&Bj(U(Fe_kzl2iOl?Y64n0gLJzKPVV`^tstx2!&)ERc zm>UC~?LK6@v>SqcWj4VLo=@sYVKk8Y^vqAPnA|>)ElMY+8qWS&y_MrD2IY}_lYvnkF9%}Wza!Tj@!SSt*?MkxW4VYC0H@GYVn}nQ+Mo2MvM6Fye?_1sv)@$cOJ!hZLOgv5y}HtxtA$$wm&Ime-*WQgG7{m4;a7a{7Z@*5}ke~wZeibW_3rK^eRzR zPuPyXOQA1*5+5b=k-~SXZE6>!=xihDvd5w(f;k}}%OO9PDpWdNRGXwGlL|ijCvAoK zhmnw&aV}RTP`0QL4Ev zRj-dk!K{ANW+fxza|$%tydblmHgQ&2A0;j3+>>@v(!rSs4r!y(G3%i9kXagwedu~f zFH}t)4E3Zd!dd-}T2;C*%7}rw>uHg3be5=&Jjr|+Cp4N=l8=Pv3tQiCE`J27md(Yx zZhR)hPlBzfElTd0AxbeANnNz~^9W*}=P}Ytb4A-}ZAw3wP0fmxBvGWxIz8q?D)>Io zETX~%p=j=ZjY3yVLB^Fdl8?S7oM+o3_4Fx9d}D<0U(b@+1$&e|Cn{Pu1qFl7(%lC$ zp?lkcYqgugA#NIK(|gjy`mP8bwVlihd9J-*Lhd_DNfFyD8dJE=+s-~q^KwxV(LoY7 zXVDPI+z=Pegegm@{f2;@u{Bbvf9Cw?eDcn$Vt?9H3Z6Iw;`W!+wqBz!mf zLHtqufb;Gb-jdHL?)7+HvU8proSV4knmIt|ElMPpvj6#%{}awvH^O7;W>~ZDRC&pp z{GVlzL1j;>Jla48PmSSRv4|ll?>5nsKOhoeJ$# z&Kzy?L!9&%YW&TQLLatL)nB1dem`G$e+psG$27=(?<5s-c*<6DW^LYNO6lu~69q$2 zcK!l+?^pxbLgv4$9Y_Y9KM1vodxoc+9X_}S@p~0erf5-V*hZ9}KS;iTuSvb5oJxNj zfzp@HsO|Uzs%e=GgLQj_e9>j04Esv6{kFp97tYCrvkxoz3O!)(|*s@`<(5m2;^voO4b0E|FyFN0C&SC|nu9-0VyKIi*~4k0w{OHR_m4qSEez+0otzt?)&m`F4_j%9j}|_a)b3yU1>P zIQ&k{gQBuglz5D$BBAj^&kFQb4E_0q!cM&@eMB_xSF&M6Ik2c%u6vf`S^bImd z-YaAlvFsgf3P-{z4~p8t{qE$2LfTL!T9WF?_iQSKpKha!3tL4xM&LpC3Rt%C9d>3h zOqOh<(`((3Rezs${@V|yE!W|ST)}x1OVVKvhGCm0RHQF-YnCINISi7IgJiycHBrfq zp`2I0C{xE+Qm7OE&P!a#$GQtz*z;>`v>0(^nPfgH3^9X35ximz^TC{OeYL>F$y1Ru zJD6(yxLdvo|%Z_#T?g{2X0M5$%4P zp?shRqBzd zYmJY=E1;HoQJQBj7#QxQl7LpK*4;&U#~R7SXAI*G-srvzk)B(`Ju`DH4~l?ra%^WIm%(^`?;9L5RaM(q^4kM={+L(aLbPmu+_iJ$=YMV!B3K3Eqk()M=8@NPr6zxAco z9y+*w%8v7Xhe>-dd+jINkj>-IqP-vhL)LuQ+aDLL24TdT4cLuTRk- zAw4vg@^WvH#?lF?X%R?@EEie-*`eYE^V4#RL}L73l(Bj%Zb_FQ+@I%7Z+D9hhg7O{ z9U!v8>M2v_GVSyZfqT|t;#scjO3HFb;y=pdsvAOY^?ph^KAnP(Y=+{ObHXat0G3;Q zkXFE&f!7bh{6$|R_gjX{i(ffwp@i~}bkQ_$Dg4i@fb^R=Nj4pI)I?1~Ip>zzt^8nK z+6%JhZz+{MVd@WK(e{@eO!y2k899tXZ5m0Vh!&9(`cU}B$!Ht>n1VmDC)8Dff(Pui zvQ3uBz0yS7iCoI${8GsoLH5=GR2NU^+HEQnLslcaAf2)|FGXyFISO8-^1tm4o85PW zPFx?V88;2t%6SM(e#0}@b*Nl88K-$ZBKc8CCPM<@pPxjPpO#Q;Dd)q~{a|{0927-I zNV%W=AGfxN)EOh7{&v$>=!?ehQqRQZdcvLUGX zc|7u-_u`yJu56!6FI-Xdh40C1$}&ixs6N$HQkFr1yZ>Yc!fMv>W0*x#MF|$VXs$9t zpxG)a5#3-=xs+_$xO5Sl-os-}cMX3kpqLJyI1`Vti}ZzHdFJlX%CfOAPG3g&#K z?0o8AdJu_TBCQ`|2{@&*(h4I2QNJyAOl>^tp?b#fd4(I-D=5*m%J0DqT+)uA; zXGY~2Y8(0w*&c5t+s>C%ki7vW>zJ+AFanj)Lz(OFfm()rAS=!xKIrQV>jkDrKeLAt z$DU`V?nuu3&q4Xsa3oD-UP6ZsS--MH;(sb?u<47SmbIw;cAc|TV=!caHgtyo1_~#t zxDke&$2Q1}K1Y7p>)_oD6uF@f4bPLqFOl!k9$nCRAP6bUQc!$(L^jOBlpd(0jnnUXn=Yj4pH>Lm`8 zWw#WeCQ{0uBZK1IMNyD52I&K%;dF2SBwAK7{rBV1zJU3B2Q*Z6_Z0uBIH&DRmQ%-5>Z%$#9Ak`zLyl-&#(n=-TNJHlZGUB7Bw8ksdK`OyLYc%enEU%V zy^yKltjG2l@LoTf0_H7;d9pRsJr9b2$IRiXaYRQ~Jv2_-N&cl~xEQmQXU&&{{(MJN zE{J38PD)`(J&=$cK&m6Rs4mzEKCElh`i-QFyLUx~Q99|&ap!aNmZ*9-3|@x zJ{7A`ku@A)U#G(J0dsep$HQiQ3Hc_Tqs-~kaoTVadsohp*SW6Pm%jupuP5N$a(<3V zH;I;>Jf~dCoGSGd%3Q{uWswb;a+OXr%8{Q_`jF%vKCWN$Wsz zKd*=DNt-z*I0&lEQOM{z9hnBNxeDaDVg~bo*bhB?xF2kWrU{3Ay&*Ym1z-6&vMcd{ z=h+rAJ9mUXUx9@4KtXFhm3$rv_1^zP#;i6no0`UG&%Gqo=d9<}bh(ZNKchm!~cg7&}3$r-~&8M^> z0eJn(9JqIRNG1ilC}bu33arZCeVKE}dVS8;c53eOJtFRgK{@4% zPzUT4i7V&8a%=#)CIusX|87z?4-g4QPg3RZwN$IMN>nx^QE1o2$RE%Hss+E1U!YBG<&G;gi-4nYiK!mqok^x;x_+489)8X%&lA@FZe+gs&`Rnwf#r| zz4}1??;7OfTqKFXIGM#VYxd7;@w>c9=6Bx~W^0*|`uQx`ZDY?@z6Y`nafV@XnJC$_ z6%V-IQgqX%gsB&(esCBpIXiE`x{T&H^CX9kV-I7#FlBat4(smmtlO(b%!cH~Hb=$s zc`*IV=hV5`6#T0Rg1FbpH|h|Uj7#+3JoYB(x>z9fiw*fZT_ca2KIpnki86~XlrblcB>s>E z`tiLoq(d~Yr!jQ;J5jR0h-?xoNlUsC%ER3#`$IQ0Pqik=a^|`n@5=tqw^VrR3N>FF zKzcQWB(Gs!+L&Wh@OuX>889E(Cnv&%Ya-Xyp)fFcCv3bflFg`ck(|5+)m$T~U(F!{ z>w{$8Z3C(sH^DMs6e-5Bk6>OpsjQ-z3BrBQWH<3{=3?j;vd&m6Bh5Sqif4bEOYBNC zwN#PK{3F6D{XRuKnv9ZpoU{G#R7^an!?V1X)OcV6Rgbz(k<<56q31VJzObXLlzXI+ z9ukt!^>oZ)F6_hjJO4YELKoU#-xcn8gXcMh2366mosEWjoacBfMVw7ng!kzJ z`%!*q?7{4~6_H3>kjt5(YFXI;{_gIcqtmtg=ZkieS0?9+PWFM}9tRA`V%{%%VhU>u zDfRs#^5ofNBHbqEH3_7uGm_bl*5&t+>#xj3P#p9nof%=c^-P~V#oU9XIio0uGkDAO zplPa>**ttjs;e`E*7Y}3{rVr0*d29LX$Di^DfV7zV(EXJ=giLr%vmfZ&7@00`)6jH zor&O?y#ahi4MOqoH1eqg2S9fZM`3=#N|d-J;fkS_^g zAGbGcX3m#<%M#(+?;ADExI$hBxo+>^di)<3G32~IRFBRHUDJiIpIb=khUKI`k7w@l zgLuZh63*S(yS`hWluw!{W7u+07WP(jE(_+~X(7x^Z!mA=J{26@LFIF}UUMHRn$s5{ z^v?pyWctsss^!Q#bdcVy*@Swd>zrp9L6-K+OPs9_gDn+8)fx`(LMe7fc%wNml(mD+ z%)tsqe%%N}o~|NqX9Y|vrb98hflThm`92s++FO>Q{4x76ZvIW0x8p_5$X&D~T8i?2 zheJB_h;Y9ef!j3LiZGwe%S2TR>IB+<^4C9N3B9_Bn@;<$y5oiRq%#~(OHwGoyhYsu#1 zVXFOwYn`nwWK_!YAtMQlN}rOxmlJN8u7T&WyVUaN9cM`8bdh^P?;4`6D^|hA;utlX zx-<9tM=E~Gbz?;x8AT^k*{5S7cR_lK^nou&IX@ze-@%)4}b?38`*-wT*JzG*%t`a#%;%Ur^#jxY^ zwmo|ZLi%cPZKEe5;}c2QFP9D*MMD+fMS0pe^sf6{)}2kMF=P~(*M;MHy$RxG6jO8V zJK=it6lrP}plx^#=_K7HL#v)}&MYET=V_VFAvHN~849(*TcL67A*3_7Pgu~8@@}#h ztC(lW@62Rj?XHkU_oWWvS>~f+QvSi2yPqZ_>0=r9#ms)pJxh5LEMRc3P)HY!fbZ1@ zB>6TL8N0MO13nMtNoEM`HwYd#n7=gCThvtWe0?(eY70X|CHsVB+y`o94@F_|WqS9| z3bb{3Oqm0RB5~t+k}TyKC4ChYr2j=WN5?@EI$e}+T?_U1IZ+C_2+&K2ba_)R@GbL1dkY_SylFrY7`7#TXb&exdt)9$t zOB$vBaDkLh&6$fC0L`7TNXylO&o#bGX6~az+b85YD2(KGtRtDW|9OQ|kc!2xGoTF6oypZa= zI1@Z#qRfmljHZ1gP)bci$GsSoZDIy+KIhWs#EUi)J)V*3!0^sg_E=hr*Uy&2E3Yq7 z{#Xcuzmi0F(nqSF-AXOB%$U0MJu_AK{Z)iP6RVGMS3NYV=itGnc_^^DNGCj_pcrgO z<#~;CmhaDqFz)fcH{tp8WpS~uF_g=yNOnyN%BM5P@<)I2JG%suHJLJpPt5ro(jpWE z#z_6=HNT%Gkmd5d*05hzd7f+E`Mz-Vb!8?_6WQfYXSOFZ8ZJzN`N8#YH84XG=d^4* zojGsHY&@9_Xxk)6y0^%(h7uiH=Y~*b_I5~yqH^&Uy4!e@^lSb(!zI2-irH<3}%pR5kB-}=8o zVcE%nnp_gUiaDe;LO3Vlsu6A zR(?+K53VFxQ3%ZI_LHOr;))J)H~O|wsP1a2|D&3Amhp~)5axEbUL@zAYRLM@Ow_k5 zfu&wCb!=e|i%l4uSF&fl-%Doa29ujLdw33P6zS(U<8a%B!XM~x|6@pL+PbiKwTk() z>nL&{p!0h@nG^<7eakbFsRY6+#y zZMfc~%_MtyldkuQK(S&gN=GM=q%J|6_!a`GJCvYs zZ=n{F0xHOA7QYYA*HhcMVI&-A=)wPl^?PRi=Io}7*&5;eiW!TCd%@*0KgWTM!luI% z_D2m!?ea~?Pgas^?=Y?nIak`c5kndx5n4W=3Jz%tS3hrW>d^DOEd?h z3)O9B^5Cpawgb=B`8;!Pd_v}xuFNHAqsX&|DDJ;Gn0?a^PB)lea{M^e?2SRB_IR2& z#s!^u)<_Lv9;Dkwv_56e#^2wedd9z0J2_V*$vz0v(f#3nbQ@Lv-Gka*$&o2LNTCDR zKf1#Rsrjt!-YgXIKE{-^n|ZMXALx3n67G?Q>GXEiK<{i5B{RkmX}nO%ev#Vy5@fPI z(bSK1z{hteHEIKeZ8t^4qxtZ&9?$&&^Lm+Oa+me{mi-qf=!^+-r*={Ey~m=sjeRTg zY8|WBa4uoQ4q;OEFXt27nYm%eJ68VVj0VqHJsQ|MvYmb0y*WGGi#nODpA_p)_9F&S zBj;qBpRq3F?2Eu-&q>Z)BekQ3v^W>Ed2E4Fe!-Q@3Rl(cjr9i?PekI_>M{no>6Mz7}BUW zh=5b;(5&H_SIZNAO$yX=&OsGDTa-NCL#e;4Bh3z?B|3&MsUJnRK8%C%+Cp+)e1xR2 zternjCIi+xOPdBkvEvAZYbQH?eA|V;uP93(pDC7JEL7OJ>{BSl(K73CG zkLHl;Ca!V5F65miBcXc8ys;wQEl|;26b3Gb5A#>-&%08>`O(N+%p8wrd#U~<*W4Rc zqxRHW;a)WiwWqV_`j{24JM4vwi+oQ{%_hZ-sW|Pl4EbryS!wEljN4;`Q5mzEPP9{w z%_c~ekAbGQ7ul9QB=e;X2sKa(DQErVW|hKxPb{g;*?-n;B6gn+N6>u*f*rESV(m-> z4E8|W{t`+()dTWezOP+0Wc_v&4A^Ja#G1M4=|EX?_h>}wGOI?+K(lupbv*x#G6o-} z@NFj0>RLwy-^|E)8$ovLBXth2;XB!Y3PSIba`Sq!8Gc#RzuSm#+e=h6Y&d%sZ_8@W z^@0EK?=ajf5*bgtp}lq-5+;wNNFQbc=B^TY_n%Su*i}e9O=Cr5yB#% zFG>OgayBz_>)kqJ-RCUy*J5%i{vLTe>yYwJBdyUJahAC{$_vcY=)?7uZwlG4_agDy zXc+zK2}PM4D)--nyl^=suQoF>|dt&jY3o0ka51@`GoNgx{y z;cTxBSdvY!dC4r39-~RNEE<&>XOcYD;atEW(&3#ts>dtf`N@y<{BPvhdp+`l-C#M% z67Tfg;CifzVrQG8bnjh~>pY@x&rtY#9wM{TjxhJhBbga{vsBy3?t~dUt&7MY_ZsiX zsS=*29+LDzfhbAl{`Jg35&ncdFgwHW;Q3l}7M?_A6yBPz)Mf)=**D z0$A(zgS4Hm?ONcfRa`0p?}kpYtm)VEUOUZ+6O4L!-VR5 z*mLL*EAvmCi)PL{5TPHFqUjzBE^DQg)D%A2)l_w+=P%PXPOo`bqqv(Wa*f|9n(B|A4hvk!*D zx$-D^ZQjh@KF+pQ=?jBhzmg?;{sL3A*|*P{)4m^MT0eAB_O;$Q<`bVmL|BY;e z?~zSQ5xFuyAu9O=6>klJ5%+oaH+kQX%VspEkEg@~FG%v=MCdPY9^Ir_9a^J|B7u*wC?C(o-WM;neR%o$TH=*<@=ZV6g zwfz9!0|Sw`bQp{-2EmE5+6|nMPU_f-RNJlOJ!LBy>>Nc}%seef=||<#rL0e+QPxyF z8e(P)zm5q=aGp&%ZQ;;7SxfF!$EiZsj^_oFh3y{?N%CNn@Lza}Bv$EE-RC(i>Cf}k zG9QEn9}wAUU6eJL?HJH>WDmr}4;T1g*idH`=9p?+54EC~gn!(xNPvQ{^*-jy3aouLgUZ89@ZMf#&r*aL? z_3HX?-#v$Syj&DI4>@^iWZ?{iVHq=-@sX-UXZN2>UekulY^NxFBPLmW8RarW@J%@UzBL9p3IzM z7v6&r4&w)X5cRiSr&8z9O z`K2BAhmXno!zMcXbTF#*Qc11*S-8c{K(=9jv~kX|{>B$7Q$H1@>i)=J|6>C`gSBTD z3;Dd0bYd^>t@-IDmFn>S`9L0Jr0o>V8?-4$ItrfbhiX(LQuqrgf+mk+_TNpi^!A{P zeSPu2oL-Z$ib{IjBt<7tKn3d|E)L9k|@Asg};69WvY(L5J zece&*M6pUH}=|GvZoSeoHQ)poi7WVsC3s= zvf_Pbo|+mm*>sO&ds|8OG5hF#{zzFb`97R>nHolz@<6dWG{GZ8h_4AICiQ`jzXGzI zThTCZF5=EG?^P_I%J|hVnDB^VDmtr}AG#uQU+N5R&fBT_CX0loYs_zTq$~TF^Ka!YERy;BQF*{VB!&uS%tv5F4yj69 z$>`e!s^)xqqMkikI4d`Mp*CbmtU+FpQDpWUiv7(Ffy}dNy3TyO-*h-fZ3o+RyU4(* z2h1-QQu0yu_`e>D>$8}{@vtXCYxfJ66`N3EvXkQZOxA>c5=K!!P}FJOJv4|p$?ts; z`y?9Sr`!eAZ&Cgq zBhbA|o7Oz%^6W z^3^CfvW4e#N;Ehbp`&I$#cfIE9Xf{41P&IN7kCB{dde~UclNG-8c12|Mv`iKh)A0{ z2L8J2nfbVzTK@Zi>MyYd%g==2wOOduuM)}RjC@BKGKz1CinW80v)&Bln^!{eq{&g9 z*CKN(@<8&I?@?BmFO07#aoCCZQ1$aEaoq+;yL=M*#Qm$Xl8jqtqo8Ru1z+SEXTUw7 zbKDad1LjioV9ts6ZKulLlj&ILBD8G3O41XNqB3nU;@f+oW4+YFN+8ZO$8akg@YYnpek336mnunMNo`^hol#&~G2bt+G zG>+0I_1kO_S>s7HbNC(TV9w*_6}Z^T5Fc+6TF!Pb6F!in4i7}DF>CFcQ$=xn7nta5 zp;*>oK3=iq`G|&lpf55_fIo@{{z$>PtV5g}D2#qTOJ(!Bka3L`s`uZfvewHK**1!F z7W0mYLx)8nGZa#_>d5cK8fYzX$L4`+5ShVT0-c4FxAqw|C$hgJ-jZT?2bhUIvk})X zpwz6cr0@u$_zOeef1#1Mf1KEw0vMho11&O2p7yNl#m5zK$< zirDu(QGYRxn)3bO=EYj4J7*B49i=4iv7}n^pHMIPLiKGMkzz9k{xaq!|N0|^PdiSr z0sT>Ul6@+6O(N`IUnCv0Vm+jd+Qv;~ztIJm#nwJZnpPz4u5jTUFUHJLc_*x!rsCKZ zo{M)eK;S=9$m!W66jaVcq@@`;c64Wt``_d~fP36S_ejEd?2?|Q@R{6?_wvlbM+b9I zGHU~`&yt@%_xW*tB7wPYsUH-AU!rOgqP|#);AW z;BI4w%4tKGU0xwh95zB^;0+2)+(505mf$q=(=A62Wu2uL!qrm{!hLJ#-%Uc({T7uo zZ{N7VXlxARWvE%kJ)Ar#hIhojm4j7WOj6SXFb zgyM<|#RoFa^ZNG)9P}?Wv)@NmnkY(V6_7#`D;mD6L9^=#VR?la_Y)UGuJ1$XUqjFw zc8>kg%#$9#SquMp2=>24QT8p&(&dcT?K5QXO9JJ$jDy+Kc#{0fd41U%q5453YiM1B z@UN!)f2tMK z;k4EpwllKn7&votrixNr+)#Yl7T!j)V0qjM+GE|}GN1hh;Vqm4yCKf*Vy@8A{*<(O zEYu5p5Z5D-q?>qV7^EdMikYO~IZ58CQcC~F53`vM6Ucf&&H^vEJ`5(EgrBH*3C~@| z&u6{g5|I~AP(Cx+!*+82997~d-Iq+Iy?Ks3jB^TEgGI)@{)p@tM$PAWUYYOA{M_!y znmU8J{xlrLHDM@o`7ErqJto?~`l2=W9RG2CF!lpw|1}2Eb*x33@qPW-hrK?=P+@Vmy>n+4GUO?*OUx{2_Kuodao=lKYP7XnDB;!R)=XPG66bJ-j!<+!({dIxt?*p?RcAmoS+9T)tXlAeZQgdS@ zWe6{!Tq%d)<)Ij2*d0pKD3O1E5Db#Oi2A@>D%;1ie)BAxa;m<6xiG5}TqY>t_5yiiB!P#;Cko;aJZ0^4nCC(G6WTrnUO*V3^!Ym^G z9c?VP(Z|IE_1F7kwqP34BP+?!iTj`1R+P}+ip)FvBk$eMRJF(!CIKO^KlnEVkN--} zfBa4wof3MN&Kbj@J*X_RH|70d4XJ7~>R0U{qpwRjD|Li)K2GN>p%u)cD`@!LkqCbk z3|Uwyl}_19t(V-9zj`hFHBTu+(nS0XuR)0O5=TGv;32oJ*F3iL=kk? zMUNpc{zfW!x`-0Kdcerim-jAhq~hPVa^J9xluJ(tRofY{ zgmZXh0di{DV2qZVe^QGZ?*%=hOO}6!FeBN8_a43?SMv-~@H=PU{)7w+m{It2Jtn^H ziq|$~i171)rEwTaJ%3daL4&j2x)7!%w z@5L~f#5<22zDN1E+mtc1RfH`wW8d30Sw?P+@Z&uQ&LeXvDx2p@1OFv2GAS5m2BJR)9z_Z9V) zb@KO}Gn94YQ|IXV0nXR2XC_?XQYf`&lgZ~~va?@+6!w6o{^m+*=ff1Z)Qk+-f9R~m z{I&LF=J3JD}qX??Eb@j*Dv@;l`fy+L!l)X}&8G&s?Si zo;_zg+C#=?d6z=Rcc6-q@ISsncJiI1V2|Wf zDi}8oRp0S04Cgo0=(z~*2Cait`bK2M&EvCwBAj|#Bm49OMA&VFT5*;&p`Nf*pC_}< zMcmiTK+#`;&>G6xVYjE0$(~rn+dV?EK9@90hajtcH{H$V+)dbVQT}rc`Cl3Zi!CuQ z=yrl07+XQ2%{!cyuzq|YiJX~(5_OMr^PzR3$)&p}+MeYfJ7;v{}3JW#9e)BatP_$dYsD28~nTsbt;g`5k8J zY0FO3SRl539ny!dU+saf`tHrU* zEhua2Np;_|2l3iBlAlWyNk$gXozNG%FSEW{yUI}_^^{3=oTTE9kqDQcC-u^F;ZV+d z60Z{qeA$21!CCeB_OLt{hOV)^GrZjsu0NZ=W3ekF_0iNieGKxyPC{OD00O?_{6wA) zV%J1NYV%YWU0@ze>o&5i3xV>bGt~8RK8xa*N5$NOUA-yvL7BKh1_(W%5y@+K7j(u8 z(Uv`zY!6F$*YSRlsm;FJ0-(PBH!|P2jO&zaQCodg1TyC%_?N*5Xq>_OvBxlDeJ^G9 zU5%po%TSWS8v0+%4G!Zy2thwl){tJ*PCOeO){C+yc|tSOShz>j^LZ1D%ul?NWj%Wz zc5yDDNgEZsgICd)**4eNH}=j{Ts%0Md!9u0vwWkl{j;&}u^vi$Pew+>cv5%Bk<@ve zr11}h=5{lARkCMs`(esDdYBYTFOtic<%n24g?Gi+pl(tiGnOxKR!$o+<9V0mb$dkC z{Xjcg`18=t5)OK%h>T_)kCh%3o$dx{?jx~8*MM2z%xWJt6)w-YmtxOf))oF9S~-I> dr!PrYPo|pwcCd7ACzYF@Q2lXErdq3q{{s^G;iv!r literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/resnet/batch-size-32_building_version-2_width-8_channels-4/model.ckpt.index b/utils/testing/reference_data/resnet/batch-size-32_building_version-2_width-8_channels-4/model.ckpt.index new file mode 100644 index 0000000000000000000000000000000000000000..af3805389d77973168c45a1da6e2e3d85c49e81e GIT binary patch literal 477 zcmZQzVB=tvV&Y(Akl;^BEJ@CY&&w~$P0Y!xN-W9D&(lvzElK2H6k-u#;$YDbFx{CS zenW&+Fg-Ch7cQ(I0F+QRKfPOoTQE1jEHf`XJ~uTn52i~&0jf`$L$EBdC^IoHITbEv z0M#nYE*Ni!Vwr*iRGS#ObpcR4V%V)qfa;UQVqF1LD+8~5a(-T!QHp+cYEfQl4$wUU zKp!$gG1#{X4U8HGSWJ6`D;BeH$^%`8N9h4pkW!V~vKb6K3YmEYr6uttsd>fuMNoqk zfCh6wF^fV2mqtSalWF?gPsR*Dzz8Cae^&5>a}Io3xPgI@Ig6p487{Vbadun}GsjG3 RevlA;5dOQNTcyq!L&W^gX0;Nu|1 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

mWgj13f^V5F* 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

8A*~PNmBFNXC#CqNm9v3GLnoW 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 literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/resnet/batch_norm/model.ckpt.index b/utils/testing/reference_data/resnet/batch_norm/model.ckpt.index new file mode 100644 index 0000000000000000000000000000000000000000..b082a6c8eee91959b0f1bf778d744bab52b7c959 GIT binary patch literal 275 zcmZQzVB=tvV&Y(Akl;^BEJ@CY&&w~$P0Y!xN-W9D&(lvzElK2H6k-u#;$YU`F`emX z%PYbvn4XxM3l~=40ZLTnH7JU33+CpRW#*;F=cXp+!E`A|K=ny;2$m%lWhUk&r^4k_ zpjsJtI?t(G5`T1h={q!VhraT o__S~X10!=5!(Ol)m{9YODm=uk literal 0 HcmV?d00001 diff --git a/utils/testing/reference_data/resnet/batch_norm/results.json b/utils/testing/reference_data/resnet/batch_norm/results.json new file mode 100644 index 0000000..c595825 --- /dev/null +++ b/utils/testing/reference_data/resnet/batch_norm/results.json @@ -0,0 +1 @@ +[32, 16, 16, 3, 0.9722558259963989, 0.18413543701171875, 12374.20703125, 32, 16, 16, 3, 1.6126631498336792, -1.096894383430481, -0.041595458984375] \ No newline at end of file diff --git a/utils/testing/reference_data/resnet/batch_norm/tf_version.json b/utils/testing/reference_data/resnet/batch_norm/tf_version.json new file mode 100644 index 0000000..8cf1ef0 --- /dev/null +++ b/utils/testing/reference_data/resnet/batch_norm/tf_version.json @@ -0,0 +1 @@ +["1.8.0-dev20180408", "v1.7.0-1345-gb874783ccd"] \ No newline at end of file diff --git a/utils/testing/reference_data_test.py b/utils/testing/reference_data_test.py new file mode 100644 index 0000000..8a39ea2 --- /dev/null +++ b/utils/testing/reference_data_test.py @@ -0,0 +1,133 @@ +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""This module tests generic behavior of reference data tests. + +This test is not intended to test every layer of interest, and models should +test the layers that affect them. This test is primarily focused on ensuring +that reference_data.BaseTest functions as intended. If there is a legitimate +change such as a change to TensorFlow which changes graph construction, tests +can be regenerated with the following command: + + $ python3 reference_data_test.py -regen +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import sys +import unittest +import warnings + +import tensorflow as tf # pylint: disable=g-bad-import-order +from utils.testing import reference_data + + +class GoldenBaseTest(reference_data.BaseTest): + """Class to ensure that reference data testing runs properly.""" + + @property + def test_name(self): + return "reference_data_test" + + def _uniform_random_ops(self, test=False, wrong_name=False, wrong_shape=False, + bad_seed=False, bad_function=False): + """Tests number generation and failure modes. + + This test is of a very simple graph: the generation of a 1x1 random tensor. + However, it is also used to confirm that the tests are actually checking + properly by failing in predefined ways. + + Args: + test: Whether or not to run as a test case. + wrong_name: Whether to assign the wrong name to the tensor. + wrong_shape: Whether to create a tensor with the wrong shape. + bad_seed: Whether or not to perturb the random seed. + bad_function: Whether to perturb the correctness function. + """ + name = "uniform_random" + + g = tf.Graph() + with g.as_default(): + seed = self.name_to_seed(name) + seed = seed + 1 if bad_seed else seed + tf.compat.v1.set_random_seed(seed) + tensor_name = "wrong_tensor" if wrong_name else "input_tensor" + tensor_shape = (1, 2) if wrong_shape else (1, 1) + input_tensor = tf.compat.v1.get_variable( + tensor_name, dtype=tf.float32, + initializer=tf.random.uniform(tensor_shape, maxval=1) + ) + + def correctness_function(tensor_result): + result = float(tensor_result[0, 0]) + result = result + 0.1 if bad_function else result + return [result] + + self._save_or_test_ops( + name=name, graph=g, ops_to_eval=[input_tensor], test=test, + correctness_function=correctness_function + ) + + def _dense_ops(self, test=False): + name = "dense" + + g = tf.Graph() + with g.as_default(): + tf.compat.v1.set_random_seed(self.name_to_seed(name)) + input_tensor = tf.compat.v1.get_variable( + "input_tensor", dtype=tf.float32, + initializer=tf.random.uniform((1, 2), maxval=1) + ) + layer = tf.compat.v1.layers.dense(inputs=input_tensor, units=4) + layer = tf.compat.v1.layers.dense(inputs=layer, units=1) + + self._save_or_test_ops( + name=name, graph=g, ops_to_eval=[layer], test=test, + correctness_function=self.default_correctness_function + ) + + def test_uniform_random(self): + self._uniform_random_ops(test=True) + + def test_tensor_name_error(self): + with self.assertRaises(AssertionError): + self._uniform_random_ops(test=True, wrong_name=True) + + def test_tensor_shape_error(self): + with self.assertRaises(AssertionError): + self._uniform_random_ops(test=True, wrong_shape=True) + + @unittest.skipIf(sys.version_info[0] == 2, + "catch_warning doesn't catch tf.logging.warn in py 2.") + def test_bad_seed(self): + with warnings.catch_warnings(record=True) as warn_catch: + self._uniform_random_ops(test=True, bad_seed=True) + assert len(warn_catch) == 1, "Test did not warn of minor graph change." + + def test_incorrectness_function(self): + with self.assertRaises(AssertionError): + self._uniform_random_ops(test=True, bad_function=True) + + def test_dense(self): + self._dense_ops(test=True) + + def regenerate(self): + self._uniform_random_ops(test=False) + self._dense_ops(test=False) + + +if __name__ == "__main__": + reference_data.main(argv=sys.argv, test_class=GoldenBaseTest) diff --git a/utils/testing/scripts/presubmit.sh b/utils/testing/scripts/presubmit.sh new file mode 100755 index 0000000..5b2aeba --- /dev/null +++ b/utils/testing/scripts/presubmit.sh @@ -0,0 +1,97 @@ +#!/bin/bash +# Copyright 2018 The TensorFlow Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +# Presubmit script that run tests and lint under local environment. +# Make sure that tensorflow and pylint is installed. +# usage: models >: ./official/utils/testing/scripts/presubmit.sh +# usage: models >: ./official/utils/testing/scripts/presubmit.sh lint py2_test py3_test +set +x + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR/../../../.." +MODEL_ROOT="$(pwd)" + +export PYTHONPATH="$PYTHONPATH:${MODEL_ROOT}" + +cd official + +lint() { + local exit_code=0 + + RC_FILE="utils/testing/pylint.rcfile" + PROTO_SKIP="DO\sNOT\sEDIT!" + + echo "===========Running lint test============" + for file in `find . -name '*.py' ! -name '*test.py' -print` + do + if grep ${PROTO_SKIP} ${file}; then + echo "Linting ${file} (Skipped: Machine generated file)" + else + echo "Linting ${file}" + pylint --rcfile="${RC_FILE}" "${file}" || exit_code=$? + fi + done + + # More lenient for test files. + for file in `find . -name '*test.py' -print` + do + echo "Linting ${file}" + pylint --rcfile="${RC_FILE}" --disable=missing-docstring,protected-access "${file}" || exit_code=$? + done + + return "${exit_code}" +} + +py_test() { + local PY_BINARY="$1" + local exit_code=0 + + echo "===========Running Python test============" + + for test_file in `find . -name '*test.py' -print` + do + echo "Testing ${test_file}" + ${PY_BINARY} "${test_file}" || exit_code=$? + done + + return "${exit_code}" +} + +py2_test() { + local PY_BINARY=$(which python2) + py_test "$PY_BINARY" + return $? +} + +py3_test() { + local PY_BINARY=$(which python3) + py_test "$PY_BINARY" + return $? +} + +test_result=0 + +if [ "$#" -eq 0 ]; then + TESTS="lint py2_test py3_test" +else + TESTS="$@" +fi + +for t in "${TESTS}"; do + ${t} || test_result=$? +done + +exit "${test_result}"